diff --git a/wetty b/wetty deleted file mode 160000 index 0ec642a..0000000 --- a/wetty +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0ec642a27302bb4c53244715e089e12a7fefe199 diff --git a/wetty/.all-contributorsrc b/wetty/.all-contributorsrc new file mode 100644 index 0000000..8b0d6c5 --- /dev/null +++ b/wetty/.all-contributorsrc @@ -0,0 +1,387 @@ +{ + "projectName": "WeTTy", + "projectOwner": "butlerx", + "repoType": "github", + "repoHost": "https://github.com", + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": true, + "commitConvention": "eslint", + "contributors": [ + { + "login": "butlerx", + "name": "Cian Butler", + "avatar_url": "https://avatars1.githubusercontent.com/u/867930?v=4", + "profile": "http://cianbutler.ie", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "krishnasrinivas", + "name": "Krishna Srinivas", + "avatar_url": "https://avatars0.githubusercontent.com/u/634494?v=4", + "profile": "http://about.me/krishnasrinivas", + "contributions": [ + "code" + ] + }, + { + "login": "acalatrava", + "name": "acalatrava", + "avatar_url": "https://avatars1.githubusercontent.com/u/8502129?v=4", + "profile": "https://github.com/acalatrava", + "contributions": [ + "code" + ] + }, + { + "login": "Strubbl", + "name": "Strubbl", + "avatar_url": "https://avatars3.githubusercontent.com/u/97055?v=4", + "profile": "https://github.com/Strubbl", + "contributions": [ + "code" + ] + }, + { + "login": "2sheds", + "name": "Oleg Kurapov", + "avatar_url": "https://avatars3.githubusercontent.com/u/16163?v=4", + "profile": "https://github.com/2sheds", + "contributions": [ + "code" + ] + }, + { + "login": "rabchev", + "name": "Boyan Rabchev", + "avatar_url": "https://avatars0.githubusercontent.com/u/1876061?v=4", + "profile": "http://www.rabchev.com", + "contributions": [ + "code" + ] + }, + { + "login": "nosemeocurrenada", + "name": "Jimmy", + "avatar_url": "https://avatars1.githubusercontent.com/u/3845708?v=4", + "profile": "https://github.com/nosemeocurrenada", + "contributions": [ + "code" + ] + }, + { + "login": "lucamilanesio", + "name": "Luca Milanesio", + "avatar_url": "https://avatars3.githubusercontent.com/u/182893?v=4", + "profile": "http://www.gerritforge.com", + "contributions": [ + "code" + ] + }, + { + "login": "antonyjim", + "name": "Anthony Jund", + "avatar_url": "https://avatars3.githubusercontent.com/u/39376331?v=4", + "profile": "http://anthonyjund.com", + "contributions": [ + "code" + ] + }, + { + "login": "mirtouf", + "name": "mirtouf", + "avatar_url": "https://avatars3.githubusercontent.com/u/5165058?v=4", + "profile": "https://www.mirtouf.fr", + "contributions": [ + "code" + ] + }, + { + "login": "CoRfr", + "name": "Bertrand Roussel", + "avatar_url": "https://avatars1.githubusercontent.com/u/556693?v=4", + "profile": "https://cor-net.org", + "contributions": [ + "code" + ] + }, + { + "login": "benletchford", + "name": "Ben Letchford", + "avatar_url": "https://avatars0.githubusercontent.com/u/6703966?v=4", + "profile": "https://www.benl.com.au/", + "contributions": [ + "code" + ] + }, + { + "login": "SouraDutta", + "name": "SouraDutta", + "avatar_url": "https://avatars0.githubusercontent.com/u/33066261?v=4", + "profile": "https://github.com/SouraDutta", + "contributions": [ + "code" + ] + }, + { + "login": "koushikmln", + "name": "Koushik M.L.N", + "avatar_url": "https://avatars3.githubusercontent.com/u/8670988?v=4", + "profile": "https://github.com/koushikmln", + "contributions": [ + "code" + ] + }, + { + "login": "imuli", + "name": "Imuli", + "avatar_url": "https://avatars3.githubusercontent.com/u/4085046?v=4", + "profile": "https://imu.li/", + "contributions": [ + "code" + ] + }, + { + "login": "perpen", + "name": "perpen", + "avatar_url": "https://avatars2.githubusercontent.com/u/9963805?v=4", + "profile": "https://github.com/perpen", + "contributions": [ + "code" + ] + }, + { + "login": "nathanleclaire", + "name": "Nathan LeClaire", + "avatar_url": "https://avatars3.githubusercontent.com/u/1476820?v=4", + "profile": "https://nathanleclaire.com", + "contributions": [ + "code" + ] + }, + { + "login": "MiKr13", + "name": "Mihir Kumar", + "avatar_url": "https://avatars2.githubusercontent.com/u/34394719?v=4", + "profile": "https://github.com/MiKr13", + "contributions": [ + "code" + ] + }, + { + "login": "cardil", + "name": "Chris Suszynski", + "avatar_url": "https://avatars0.githubusercontent.com/u/540893?v=4", + "profile": "http://redhat.com", + "contributions": [ + "code" + ] + }, + { + "login": "fbartels", + "name": "Felix Bartels", + "avatar_url": "https://avatars1.githubusercontent.com/u/1257835?v=4", + "profile": "http://9wd.de", + "contributions": [ + "code" + ] + }, + { + "login": "jarrettgilliam", + "name": "Jarrett Gilliam", + "avatar_url": "https://avatars3.githubusercontent.com/u/5099690?v=4", + "profile": "https://github.com/jarrettgilliam", + "contributions": [ + "code" + ] + }, + { + "login": "harryleesan", + "name": "Harry Lee", + "avatar_url": "https://avatars0.githubusercontent.com/u/7056279?v=4", + "profile": "https://harrylee.me", + "contributions": [ + "code" + ] + }, + { + "login": "inducer", + "name": "Andreas Klöckner", + "avatar_url": "https://avatars3.githubusercontent.com/u/352067?v=4", + "profile": "http://andreask.cs.illinois.edu", + "contributions": [ + "code" + ] + }, + { + "login": "DenisKramer", + "name": "DenisKramer", + "avatar_url": "https://avatars1.githubusercontent.com/u/23534092?v=4", + "profile": "https://github.com/DenisKramer", + "contributions": [ + "code" + ] + }, + { + "login": "vamship", + "name": "Vamshi K Ponnapalli", + "avatar_url": "https://avatars0.githubusercontent.com/u/7143376?v=4", + "profile": "https://github.com/vamship", + "contributions": [ + "code" + ] + }, + { + "login": "tnguyen14", + "name": "Tri Nguyen", + "avatar_url": "https://avatars1.githubusercontent.com/u/1652595?v=4", + "profile": "https://tridnguyen.com", + "contributions": [ + "doc" + ] + }, + { + "login": "pojntfx", + "name": "Felix Pojtinger", + "avatar_url": "https://avatars1.githubusercontent.com/u/28832235?v=4", + "profile": "https://felix.pojtinger.com/", + "contributions": [ + "doc" + ] + }, + { + "login": "nealey", + "name": "Neale Pickett", + "avatar_url": "https://avatars3.githubusercontent.com/u/423780?v=4", + "profile": "https://nealey.github.io/", + "contributions": [ + "code" + ] + }, + { + "login": "mtpiercey", + "name": "Matthew Piercey", + "avatar_url": "https://avatars3.githubusercontent.com/u/22581026?v=4", + "profile": "https://www.matthewpiercey.ml", + "contributions": [ + "doc" + ] + }, + { + "login": "kholbekj", + "name": "Kasper Holbek Jensen", + "avatar_url": "https://avatars3.githubusercontent.com/u/2786571?v=4", + "profile": "https://github.com/kholbekj", + "contributions": [ + "doc" + ] + }, + { + "login": "khanzf", + "name": "Farhan Khan", + "avatar_url": "https://avatars1.githubusercontent.com/u/10103765?v=4", + "profile": "https://mastodon.technology/@farhan", + "contributions": [ + "code" + ] + }, + { + "login": "jurruh", + "name": "Jurre Vriesen", + "avatar_url": "https://avatars1.githubusercontent.com/u/7419259?v=4", + "profile": "https://www.jurrevriesen.nl", + "contributions": [ + "code" + ] + }, + { + "login": "jamtur01", + "name": "James Turnbull", + "avatar_url": "https://avatars3.githubusercontent.com/u/4365?v=4", + "profile": "https://www.kartar.net/", + "contributions": [ + "code" + ] + }, + { + "login": "deanshub", + "name": "Dean Shub", + "avatar_url": "https://avatars2.githubusercontent.com/u/2688676?v=4", + "profile": "https://github.com/deanshub", + "contributions": [ + "code" + ] + }, + { + "login": "lozbrown", + "name": "lozbrown ", + "avatar_url": "https://avatars3.githubusercontent.com/u/9961593?v=4", + "profile": "https://github.com/lozbrown", + "contributions": [ + "code", + "example" + ] + }, + { + "login": "sergeir82", + "name": "sergeir82", + "avatar_url": "https://avatars0.githubusercontent.com/u/5081149?v=4", + "profile": "https://github.com/sergeir82", + "contributions": [ + "code" + ] + }, + { + "login": "kmlucy", + "name": "Kyle Lucy", + "avatar_url": "https://avatars1.githubusercontent.com/u/13952475?v=4", + "profile": "https://github.com/kmlucy", + "contributions": [ + "code" + ] + }, + { + "login": "userdocs", + "name": "userdocs", + "avatar_url": "https://avatars1.githubusercontent.com/u/16525024?v=4", + "profile": "https://github.com/userdocs", + "contributions": [ + "doc" + ] + }, + { + "login": "janoskk", + "name": "Janos Kasza", + "avatar_url": "https://avatars3.githubusercontent.com/u/1554533?v=4", + "profile": "https://logmein.com/", + "contributions": [ + "code" + ] + }, + { + "login": "DefunctLizard", + "name": "Grant Handy", + "avatar_url": "https://avatars3.githubusercontent.com/u/45475651?v=4", + "profile": "https://grantshandy.xyz/", + "contributions": [ + "doc" + ] + }, + { + "login": "LeszekBlazewski", + "name": "Leszek Błażewski", + "avatar_url": "https://avatars.githubusercontent.com/u/34927142?v=4", + "profile": "https://github.com/LeszekBlazewski", + "contributions": [ + "code", + "platform" + ] + } + ], + "contributorsPerLine": 7 +} diff --git a/wetty/.dockerignore b/wetty/.dockerignore new file mode 100644 index 0000000..81d8bd1 --- /dev/null +++ b/wetty/.dockerignore @@ -0,0 +1,12 @@ +node_modules +.git +.gitignore +*.md +*.yml +*.png +**/*.conf +**/*.service +dist +build +docs +Dockerfile diff --git a/wetty/.eslintrc.cjs b/wetty/.eslintrc.cjs new file mode 100644 index 0000000..96412d8 --- /dev/null +++ b/wetty/.eslintrc.cjs @@ -0,0 +1,95 @@ +module.exports = { + extends: ['airbnb-base', 'prettier', 'plugin:@typescript-eslint/recommended'], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'prettier'], + ignorePatterns: ['dist'], + root: true, + env: { + node: true, + browser: true, + }, + rules: { + '@typescript-eslint/no-unused-vars': [ + 'error', + { varsIgnorePattern: '^_', argsIgnorePattern: '^_' }, + ], + '@typescript-eslint/no-use-before-define': ['error', { functions: false }], + 'func-style': ['error', 'declaration', { allowArrowFunctions: true }], + 'import/extensions': [ + 'error', + 'ignorePackages', + { + ts: 'never', + js: 'ignorePackages', + mjs: 'ignorePackages', + jsx: 'never', + tsx: 'never', + }, + ], + 'import/no-extraneous-dependencies': [ + 'error', + { devDependencies: ['**/*.test.*', '**/*.spec.*', 'build.js'] }, + ], + 'import/order': [ + 'error', + { + groups: [ + 'builtin', + 'internal', + 'external', + 'parent', + 'sibling', + 'index', + 'object', + 'type', + ], + pathGroups: [{ pattern: '@ev/**', group: 'internal' }], + distinctGroup: true, + alphabetize: { order: 'asc', caseInsensitive: false }, + }, + ], + 'import/prefer-default-export': 'off', + 'import/prefer-default-export': 'off', + 'linebreak-style': ['error', 'unix'], + 'lines-between-class-members': [ + 'error', + 'always', + { exceptAfterSingleLine: true }, + ], + 'no-param-reassign': ['error', { props: false }], + 'no-use-before-define': ['error', { functions: false }], + }, + settings: { + // Apply special parsing for TypeScript files + 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx', '.d.ts'] }, + 'import/resolver': { + typescript: { + project: ['./tsconfig.browser.json', './tsconfig.node.json'], + }, + node: { extensions: ['.mjs', '.js', '.json', '.ts', '.d.ts'] }, + }, + 'import/extensions': ['.js', '.mjs', '.jsx', '.ts', '.tsx', '.d.ts'], + // Resolve type definition packages + 'import/external-module-folders': ['node_modules', 'node_modules/@types'], + }, + overrides: [ + { files: ['*.ts', '*.tsx'], rules: { 'import/no-unresolved': 'off' } }, + { + files: ['*.js', '*.jsx'], + rules: { + '@typescript-eslint/no-var-requires': 'off', + 'import/no-unresolved': 'off', + }, + }, + { + files: ['*.spec.*', '*.test.*'], + extends: ['plugin:mocha/recommended'], + plugins: ['mocha'], + rules: { + 'import/no-extraneous-dependencies': ['off'], + 'mocha/no-mocha-arrows': ['off'], + 'no-unused-expressions': ['off'], + }, + }, + ], +}; diff --git a/wetty/.github/ISSUE_TEMPLATE/bug_report.md b/wetty/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/wetty/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/wetty/.github/ISSUE_TEMPLATE/feature_request.md b/wetty/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/wetty/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/wetty/.github/dependabot.yml b/wetty/.github/dependabot.yml new file mode 100644 index 0000000..74be337 --- /dev/null +++ b/wetty/.github/dependabot.yml @@ -0,0 +1,21 @@ +--- +version: 2 +updates: + - package-ecosystem: npm + directory: '/' + schedule: + interval: weekly + commit-message: + prefix: '[NPM] ' + - package-ecosystem: github-actions + directory: '/' + schedule: + interval: weekly + commit-message: + prefix: '[GH action] ' + - package-ecosystem: docker + directory: '/containers' + schedule: + interval: weekly + commit-message: + prefix: '[docker] ' diff --git a/wetty/.github/workflows/build-and-test.workflow.yml b/wetty/.github/workflows/build-and-test.workflow.yml new file mode 100644 index 0000000..71f46a0 --- /dev/null +++ b/wetty/.github/workflows/build-and-test.workflow.yml @@ -0,0 +1,51 @@ +--- +name: Build & Test +on: + workflow_call: + inputs: + working-directory: + required: false + type: string + default: '.' +jobs: + build_and_test: + name: Build & Test + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ inputs.working-directory }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Setup env + uses: actions/setup-node@v3 + with: + node-version: 22 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install + + - name: ESLint checks + run: pnpm lint + + - run: pnpm build + name: Compile Typescript + + - run: pnpm test + name: Run tests + env: + CI: true + + - uses: actions/cache@v3 + id: restore-build + with: + path: ./* + key: ${{ github.sha }} diff --git a/wetty/.github/workflows/codeql-analysis.yml b/wetty/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..4753591 --- /dev/null +++ b/wetty/.github/workflows/codeql-analysis.yml @@ -0,0 +1,29 @@ +--- +name: Code Scanning - Action +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: '0 11 * * 1' +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: javascript + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/wetty/.github/workflows/docker.workflow.yml b/wetty/.github/workflows/docker.workflow.yml new file mode 100644 index 0000000..16acb57 --- /dev/null +++ b/wetty/.github/workflows/docker.workflow.yml @@ -0,0 +1,63 @@ +--- +name: Docker Workflow +on: + workflow_call: + inputs: + platforms: + required: true + type: string + push: + type: boolean + default: false + secrets: + DOCKERHUB_USERNAME: + DOCKERHUB_TOKEN: +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + wettyoss/wetty + ghcr.io/butlerx/wetty + flavor: | + latest=true + tags: | + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: containers/wetty/Dockerfile + platforms: ${{ inputs.platforms }} + push: ${{ inputs.push }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=registry,ref=${{ steps.meta.outputs.tags }} + cache-to: type=inline diff --git a/wetty/.github/workflows/publish.yml b/wetty/.github/workflows/publish.yml new file mode 100644 index 0000000..f1eb2ad --- /dev/null +++ b/wetty/.github/workflows/publish.yml @@ -0,0 +1,60 @@ +--- +name: Build and Publish +on: + push: + branches: + - main +jobs: + test: + name: Build & Test + uses: ./.github/workflows/build-and-test.workflow.yml + + publish: + runs-on: ubuntu-latest + needs: test + permissions: + contents: write + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: pnpm/action-setup@v2 + with: + version: 8 + - name: Setup env + uses: actions/setup-node@v3 + with: + node-version: 22 + cache: 'pnpm' + + - uses: actions/cache@v3 + id: restore-build + with: + path: ./* + key: ${{ github.sha }} + + # Publish to NPM + - name: Publish if version has been updated + uses: pascalgn/npm-publish-action@1.3.9 + with: + tag_name: 'v%s' + tag_message: 'v%s' + commit_pattern: "^Release (\\S+)" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_AUTH_TOKEN: ${{ secrets.npm_token }} + + # Publish to Github PKG + - uses: actions/setup-node@v3 + with: + node-version: 22 + registry-url: 'https://npm.pkg.github.com' + + - name: Publish to Github PKG if version has been updated + uses: pascalgn/npm-publish-action@1.3.9 + with: + create_tag: false + commit_pattern: "^Release (\\S+)" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/wetty/.github/workflows/pull-requests.yml b/wetty/.github/workflows/pull-requests.yml new file mode 100644 index 0000000..32630da --- /dev/null +++ b/wetty/.github/workflows/pull-requests.yml @@ -0,0 +1,23 @@ +--- +name: Run tests +on: + pull_request: +jobs: + test: + name: Build & Test + uses: ./.github/workflows/build-and-test.workflow.yml + + validate-docker: + name: Validate Docker Build + uses: ./.github/workflows/docker.workflow.yml + strategy: + matrix: + platform: + - linux/amd64 + - linux/arm/v6 + - linux/arm/v7 + - linux/arm64 + secrets: inherit + with: + platforms: ${{ matrix.platform }} + push: false diff --git a/wetty/.github/workflows/release.yml b/wetty/.github/workflows/release.yml new file mode 100644 index 0000000..5c1dc4d --- /dev/null +++ b/wetty/.github/workflows/release.yml @@ -0,0 +1,32 @@ +--- +name: Create Release +on: + push: + tags: + - 'v*.*.*' +jobs: + release: + name: Create Release + runs-on: ubuntu-latest + steps: + - name: Create Release + uses: fregante/release-with-changelog@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + title: 'Release {tag}' + exclude: true + commit-template: '- {title} ← {hash}' + template: | + ### Changelog + + {commits} + + {range} + + docker: + name: Docker Publish Image + uses: ./.github/workflows/docker.workflow.yml + secrets: inherit + with: + platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 + push: true diff --git a/wetty/.github/workflows/stale.yml b/wetty/.github/workflows/stale.yml new file mode 100644 index 0000000..b7b13c6 --- /dev/null +++ b/wetty/.github/workflows/stale.yml @@ -0,0 +1,22 @@ +--- +name: Mark stale issues and pull requests +on: + schedule: + - cron: '39 10 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v8 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'Stale issue message' + stale-pr-message: 'Stale pull request message' + stale-issue-label: 'no-issue-activity' + stale-pr-label: 'no-pr-activity' + days-before-close: 21 + days-before-stale: 365 diff --git a/wetty/.gitignore b/wetty/.gitignore new file mode 100644 index 0000000..3aa27ee --- /dev/null +++ b/wetty/.gitignore @@ -0,0 +1,146 @@ +/build + +# Created by https://www.toptal.com/developers/gitignore/api/node +# Edit at https://www.toptal.com/developers/gitignore?templates=node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +# End of https://www.toptal.com/developers/gitignore/api/node diff --git a/wetty/.husky/pre-commit b/wetty/.husky/pre-commit new file mode 100755 index 0000000..58b1861 --- /dev/null +++ b/wetty/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +pnpm exec lint-staged diff --git a/wetty/.mocharc.json b/wetty/.mocharc.json new file mode 100644 index 0000000..b7a0ef7 --- /dev/null +++ b/wetty/.mocharc.json @@ -0,0 +1,9 @@ +{ + "extension": ["ts"], + "spec": ["src/**/*.spec.*"], + "node-option": [ + "experimental-specifier-resolution=node", + "loader=ts-node/esm", + "no-warnings" + ] +} diff --git a/wetty/.npmignore b/wetty/.npmignore new file mode 100644 index 0000000..125d7a1 --- /dev/null +++ b/wetty/.npmignore @@ -0,0 +1,26 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz + +tmp +pids +logs +results + +npm-debug.log +node_modules/* +.esm-cache +src +*.yml +Dockerfile +*.png +.dockerignore +.eslint* +.prettierrc.js +tsconfig +docs diff --git a/wetty/.prettierrc.json b/wetty/.prettierrc.json new file mode 100644 index 0000000..b9f1619 --- /dev/null +++ b/wetty/.prettierrc.json @@ -0,0 +1,16 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "proseWrap": "always", + "overrides": [ + { + "files": [ + "*.js", + "*.ts" + ], + "options": { + "printWidth": 80 + } + } + ] +} diff --git a/wetty/.yarnrc b/wetty/.yarnrc new file mode 100644 index 0000000..a3a3d23 --- /dev/null +++ b/wetty/.yarnrc @@ -0,0 +1 @@ +network-timeout 1000000 diff --git a/wetty/LICENSE b/wetty/LICENSE new file mode 100644 index 0000000..10fc4ba --- /dev/null +++ b/wetty/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2014 Krishna Srinivas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/wetty/README.md b/wetty/README.md new file mode 100644 index 0000000..2f43e90 --- /dev/null +++ b/wetty/README.md @@ -0,0 +1,212 @@ +# WeTTY = Web + TTY. + + + +[![All Contributors](https://img.shields.io/badge/all_contributors-41-orange.svg?style=flat-square)](#contributors-) + + + +[![Documentation](https://img.shields.io/badge/documentation-yes-brightgreen.svg)](https://github.com/butlerx/wetty/tree/main/docs) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/butlerx/wetty/blob/main/LICENSE) + +> Terminal access in browser over http/https + +![WeTTY](./docs/terminal.png?raw=true) + +Terminal over HTTP and https. WeTTY is an alternative to ajaxterm and anyterm +but much better than them because WeTTY uses xterm.js which is a full fledged +implementation of terminal emulation written entirely in JavaScript. WeTTY uses +websockets rather than Ajax and hence better response time. + +## Prerequisites + +- node >=18 +- make +- python +- build-essential + +## Install + +```sh +npm -g i wetty +``` + +## Usage + +```sh +$ wetty --help +Options: + --help, -h Print help message [boolean] + --version Show version number [boolean] + --conf config file to load config from [string] + --ssl-key path to SSL key [string] + --ssl-cert path to SSL certificate [string] + --ssh-host ssh server host [string] + --ssh-port ssh server port [number] + --ssh-user ssh user [string] + --title window title [string] + --ssh-auth defaults to "password", you can use "publickey,password" + instead [string] + --ssh-pass ssh password [string] + --ssh-key path to an optional client private key (connection will be + password-less and insecure!) [string] + --ssh-config Specifies an alternative ssh configuration file. For further + details see "-F" option in ssh(1) [string] + --force-ssh Connecting through ssh even if running as root [boolean] + --known-hosts path to known hosts file [string] + --base, -b base path to wetty [string] + --port, -p wetty listen port [number] + --host wetty listen host [string] + --command, -c command to run in shell [string] + --allow-iframe Allow wetty to be embedded in an iframe, defaults to allowing + same origin [boolean] +``` + +Open your browser on `http://yourserver:3000/wetty` and you will prompted to +login. Or go to `http://yourserver:3000/wetty/ssh/` to specify the +user beforehand. + +If you run it as root it will launch `/bin/login` (where you can specify the +user name), else it will launch `ssh` and connect by default to `localhost`. The +SSH connection can be forced using the `--force-ssh` option. + +If instead you wish to connect to a remote host you can specify the `--ssh-host` +option, the SSH port using the `--ssh-port` option and the SSH user using the +`--ssh-user` option. + +Check out the [Flags docs](https://butlerx.github.io/wetty/flags) for a full +list of flags + +### Docker container + +To use WeTTY as a docker container, a docker image is available on +[docker hub](https://hub.docker.com/r/wettyoss/wetty). To run this image, use + +```sh +docker run --rm -p 3000:3000 wettyoss/wetty --ssh-host= +``` + +and you will be able to open a ssh session to the host given by `YOUR-IP` under +the URL [http://localhost:3000/wetty](http://localhost:3000/wetty). + +It is recommended to drive WeTTY behind a reverse proxy to have HTTPS security +and possibly Let’s Encrypt support. Popular containers to achieve this are +[nginx-proxy](https://github.com/nginx-proxy/nginx-proxy) and +[traefik](https://traefik.io/traefik/). For traefik there is an example +docker-compose file in the containers directory. + +## FAQ + +Check out the [docs](https://github.com/butlerx/wetty/tree/main/docs) + +- [Running as daemon](https://butlerx.github.io/wetty/service) +- [HTTPS Support](https://butlerx.github.io/wetty/https) + - [Using NGINX](https://butlerx.github.io/wetty/nginx) + - [Using Apache](https://butlerx.github.io/wetty/apache) +- [Automatic Login](https://butlerx.github.io/wetty/auto-login) +- [Downloading Files](https://butlerx.github.io/wetty/downloading-files) + +### What browsers are supported? + +WeTTY supports all browsers that +[xterm.js supports](https://github.com/xtermjs/xterm.js#browser-support). + +## Author + +👤 **Cian Butler ** + +- Mastodon: [@butlerx@mastodon.ie](https://mastodon.ie/@butlerx) +- Github: [@butlerx](https://github.com/butlerx) + +## Contributing ✨ + +Contributions, issues and feature requests are welcome!
Feel free to check +[issues page](https://github.com/butlerx/wetty/issues). + +Please read the [development docs](https://butlerx.github.io/wetty/development) +for installing from source and running is dev node + +Thanks goes to these wonderful people +([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cian Butler
Cian Butler

💻 📖
Krishna Srinivas
Krishna Srinivas

💻
acalatrava
acalatrava

💻
Strubbl
Strubbl

💻
Oleg Kurapov
Oleg Kurapov

💻
Boyan Rabchev
Boyan Rabchev

💻
Jimmy
Jimmy

💻
Luca Milanesio
Luca Milanesio

💻
Anthony Jund
Anthony Jund

💻
mirtouf
mirtouf

💻
Bertrand Roussel
Bertrand Roussel

💻
Ben Letchford
Ben Letchford

💻
SouraDutta
SouraDutta

💻
Koushik M.L.N
Koushik M.L.N

💻
Imuli
Imuli

💻
perpen
perpen

💻
Nathan LeClaire
Nathan LeClaire

💻
Mihir Kumar
Mihir Kumar

💻
Chris Suszynski
Chris Suszynski

💻
Felix Bartels
Felix Bartels

💻
Jarrett Gilliam
Jarrett Gilliam

💻
Harry Lee
Harry Lee

💻
Andreas Klöckner
Andreas Klöckner

💻
DenisKramer
DenisKramer

💻
Vamshi K Ponnapalli
Vamshi K Ponnapalli

💻
Tri Nguyen
Tri Nguyen

📖
Felix Pojtinger
Felix Pojtinger

📖
Neale Pickett
Neale Pickett

💻
Matthew Piercey
Matthew Piercey

📖
Kasper Holbek Jensen
Kasper Holbek Jensen

📖
Farhan Khan
Farhan Khan

💻
Jurre Vriesen
Jurre Vriesen

💻
James Turnbull
James Turnbull

💻
Dean Shub
Dean Shub

💻
lozbrown
lozbrown

💻 💡
sergeir82
sergeir82

💻
Kyle Lucy
Kyle Lucy

💻
userdocs
userdocs

📖
Janos Kasza
Janos Kasza

💻
Grant Handy
Grant Handy

📖
Leszek Błażewski
Leszek Błażewski

💻 📦
+ + + + + + +This project follows the +[all-contributors](https://github.com/all-contributors/all-contributors) +specification. Contributions of any kind welcome! + +## Show your support + +Give a ⭐️ if this project helped you! + +## 📝 License + +Copyright © 2019 +[Cian Butler ](https://github.com/butlerx).
This +project is [MIT](https://github.com/butlerx/wetty/blob/main/LICENSE) licensed. + +--- diff --git a/wetty/build.js b/wetty/build.js new file mode 100644 index 0000000..69a9604 --- /dev/null +++ b/wetty/build.js @@ -0,0 +1,87 @@ +import {spawn} from 'node:child_process'; +import * as esbuild from 'esbuild'; +import {copy} from 'esbuild-plugin-copy'; +import {sassPlugin} from 'esbuild-sass-plugin'; + + +/** @param {string} prog + * @param {string[]} [args=[]] + * @returns {[ChildProcess, Promise<{ret: number, sig: NodeJS.Signals}>]} + */ +function cmd(prog, args=[]) { + const proc = spawn(prog, args, { cwd: import.meta.dirname, stdio: "inherit", env: process.env}); + const done = new Promise((resolve, _reject) => { + proc.addListener('exit',(ret, sig)=>{ + resolve({ret,sig}); + }) + }); + return [proc, done]; +} + +/** @type import('esbuild').Plugin */ +const typechecker = { + name: 'typechecker', + setup(build) { + build.onStart(async () => { + const [_tsc, tscDone] = cmd('pnpm', ['tsc', '-p', 'tsconfig.browser.json']); + const {ret} = await tscDone; + if (ret !== 0) { + return {warnings: [{text:`Type checking failed: tsc exited with code ${ret}`}]} + } + return {}; + }); + } +}; + +/** @param {boolean} watching + */ +async function buildClient(watching){ + + /** @type {esbuild.BuildOptions} */ + const esConf = { + entryPoints: ['src/client/wetty.ts', 'src/client/dev.ts'], + outdir: 'build/client', + bundle: true, + platform: 'browser', + format: 'esm', + minify: !watching, + sourcemap: !watching, + plugins: [ + typechecker, + sassPlugin({ + embedded: true, + loadPaths: ['node_modules'], + style: watching ? 'expanded' : 'compressed', + }), + copy({ + assets: [ + {from: './src/assets/xterm_config/*', to: 'xterm_config'}, + {from: './src/assets/favicon.ico', to: 'favicon.ico'}, + ], + watch: watching, + }), + ], + logLevel: 'info', + }; + + if (watching) { + const buildCtx = await esbuild.context(esConf) + await buildCtx.watch(); + } else { + esbuild.build(esConf); + } +} + +/** @param {boolean} watching + */ +async function buildServer(watching) { + const tscArgs = ['tsc', '-p', 'tsconfig.node.json']; + if (watching) tscArgs.push('--watch','--preserveWatchOutput'); + const [_tsc, tscDone] = cmd('pnpm', tscArgs); + if (!watching) await tscDone; +} + +const watching = process.argv.includes('--watch'); +await buildClient(watching); +await buildServer(watching); + diff --git a/wetty/conf/config.json5 b/wetty/conf/config.json5 new file mode 100644 index 0000000..92f4d6e --- /dev/null +++ b/wetty/conf/config.json5 @@ -0,0 +1,28 @@ +{ + ssh: { + // user: 'username', // default user to use when ssh-ing + host: 'localhost', // Server to ssh to + auth: 'password', // shh authentication, method. Defaults to "password", you can use "publickey,password" instead' + // pass: "password", // Password to use when sshing + // key: "", // path to an optional client private key, connection will be password-less and insecure! + port: 22, // Port to ssh to + knownHosts: '/dev/null', // ssh knownHosts file to use + // config: '/home/user/.wetty_ssh_config', // alternative ssh configuration file, see "-F" option in ssh(1) + }, + server: { + base: '/wetty/', // URL base to serve resources from + port: 3000, // Port to listen on + host: '0.0.0.0', // address to listen on + title: 'WeTTY - The Web Terminal Emulator', // Page title + bypassHelmet: false, // Disable Helmet security checks + }, + + forceSSH: false, // Force sshing to local machine over login if running as root + command: 'login', // Command to run on server. Login will use ssh if connecting to different server + /* + ssl:{ + key: 'ssl.key', + cert: 'ssl.cert', + } + */ +} diff --git a/wetty/conf/nginx.template b/wetty/conf/nginx.template new file mode 100644 index 0000000..20c6199 --- /dev/null +++ b/wetty/conf/nginx.template @@ -0,0 +1,88 @@ +server { + listen ${NGINX_PORT}; + listen [::]:${NGINX_PORT}; + + server_name ${NGINX_DOMAIN}; + root /var/www/${NGINX_DOMAIN}/public; + + # $uri, index.html + location / { + try_files $uri $uri/ /index.html; + } + + # headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-UA-Compatible "IE=Edge" always; + add_header Cache-Control "no-transform" always; + + # . files + location ~ /\. { + deny all; + } + + # assets, media + location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ { + expires 7d; + access_log off; + } + + # svg, fonts + location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff|woff2)$ { + add_header Access-Control-Allow-Origin "*"; + expires 7d; + access_log off; + } + + location ^~ /wetty { + proxy_pass http://${WETTY_HOST}:${WETTY_PORT}; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 43200000; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + + # Authenticate user via other services (e.g., oauth2 end-points) + # + # Configuration : + # - Configure a 'auth_request' directive for this server block + # - Capture the authenticated username using 'auth_request_set' + # - Set the 'remote-user' request header accordingly + # + # Example (using lasso as authentication middleware): + # + # Add to server block: + # auth_request /lasso-validate + # auth_request_set $auth_user $upstream_http_x_lasso_user; + # + # Add to /wetty location block + # proxy_set_header remote-user $auth_user; + # + # And configure a '/lasso-validate' location. See this blog for further + # guidance: https://developer.okta.com/blog/2018/08/28/nginx-auth-request + } + + # gzip + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml; +} + +# subdomains redirect +server { + listen ${NGINX_PORT}; + listen [::]:${NGINX_PORT}; + + server_name *.${NGINX_DOMAIN}; + + return 301 https://${NGINX_DOMAIN}$request_uri; +} + +# set ft=conf diff --git a/wetty/conf/wetty.conf b/wetty/conf/wetty.conf new file mode 100644 index 0000000..2dd627f --- /dev/null +++ b/wetty/conf/wetty.conf @@ -0,0 +1,13 @@ +# Upstart script +# /etc/init/wetty.conf + +description "Web TTY" +author "Wetty" + +start on started mountall +stop on shutdown + +respawn +respawn limit 20 5 + +exec sudo -u root wetty -p 3000 diff --git a/wetty/conf/wetty.service b/wetty/conf/wetty.service new file mode 100644 index 0000000..c503121 --- /dev/null +++ b/wetty/conf/wetty.service @@ -0,0 +1,21 @@ +# systemd unit file +# +# place in /etc/systemd/system +# systemctl enable wetty.service +# systemctl start wetty.service + +[Unit] +Description=Wetty Web Terminal +After=network.target + +[Service] +Type=simple +WorkingDirectory=$HOME/.config/yarn/global/node_modules/wetty/ +ExecStart=/usr/bin/node . -p 3000 --host 127.0.0.1 +TimeoutStopSec=20 +KillMode=mixed +Restart=always +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/wetty/containers/docker-compose.traefik.yml b/wetty/containers/docker-compose.traefik.yml new file mode 100644 index 0000000..ead866c --- /dev/null +++ b/wetty/containers/docker-compose.traefik.yml @@ -0,0 +1,35 @@ +version: '2' + +# Sample docker compose file to demonstrate the usage of wetty behind a reverse +# proxy, optionally with Let's Encrypt based SSL certificate +# +# For SSL support, uncomment the commented lines. Consult traefik documentation +# for features like automatic forward from HTTP to HTTPS etc. + +services: + wetty: + image: wettyoss/wetty + command: + - --base=/ + - --ssh-host=ssh.example.com + labels: + - "traefik.enable=true" + - "traefik.http.routers.wetty.rule=Host(`wetty.example.com`)" + # - "traefik.http.routers.wetty.tls.certResolver=default" + # - "traefik.http.routers.wetty.tls=true" + + reverse-proxy: + image: traefik + command: + - --providers.docker + - --entryPoints.web.address=:80 + # - --entryPoints.websecure.address=:443 + # - --certificatesResolvers.default.acme.email=your-email@example.com + # - --certificatesResolvers.default.acme.storage=acme.json + # - --certificatesResolvers.default.acme.httpChallenge.entryPoint=web + ports: + - "80:80" + # - "443:443" + + volumes: + - /var/run/docker.sock:/var/run/docker.sock diff --git a/wetty/containers/ssh/Dockerfile b/wetty/containers/ssh/Dockerfile new file mode 100644 index 0000000..17a9217 --- /dev/null +++ b/wetty/containers/ssh/Dockerfile @@ -0,0 +1,3 @@ +FROM sickp/alpine-sshd:latest +RUN adduser -D -h /home/term -s /bin/sh term && \ + ( echo "term:term" | chpasswd ) diff --git a/wetty/containers/wetty/Dockerfile b/wetty/containers/wetty/Dockerfile new file mode 100644 index 0000000..fb8bbe6 --- /dev/null +++ b/wetty/containers/wetty/Dockerfile @@ -0,0 +1,37 @@ +# ---------- Base image ---------- +FROM node:20-alpine as base +RUN apk add -U build-base python3 py3-setuptools make g++ git +WORKDIR /usr/src/app + +# Install pnpm globally +RUN npm install -g pnpm@latest + +COPY . /usr/src/app + +# ---------- Production dependencies ---------- +FROM base AS prod-deps +RUN --mount=type=cache,id=pnpm,target=/root/.pnpm-store pnpm install --prod --frozen-lockfile --ignore-scripts +# Rebuild only the native modules we need +RUN cd node_modules/node-pty && npm run install && cd ../.. +RUN cd node_modules/.pnpm/gc-stats@1.4.1/node_modules/gc-stats && npm run install + +# ---------- Build stage ---------- +FROM base AS build +RUN --mount=type=cache,id=pnpm,target=/root/.pnpm-store pnpm install --frozen-lockfile +RUN pnpm run build + +# ---------- Final runtime image ---------- +FROM base +LABEL maintainer="butlerx@notthe.cloud" +WORKDIR /usr/src/app +ENV NODE_ENV=production + +COPY --from=prod-deps /usr/src/app/node_modules /usr/src/app/node_modules +COPY --from=build /usr/src/app/build /usr/src/app/build +COPY package.json /usr/src/app + +RUN apk add -U coreutils openssh-client sshpass && \ + mkdir -p ~/.ssh + +EXPOSE 3000 +CMD ["pnpm", "start"] diff --git a/wetty/docker-compose.yml b/wetty/docker-compose.yml new file mode 100644 index 0000000..f8d95eb --- /dev/null +++ b/wetty/docker-compose.yml @@ -0,0 +1,44 @@ +--- +version: '3.5' +services: + wetty: + image: wettyoss/wetty + build: + context: . + dockerfile: containers/wetty/Dockerfile + entrypoint: pnpm run docker-compose-entrypoint + tty: true + working_dir: /usr/src/app + ports: + - '3000:3000' + environment: + SSHHOST: 'wetty-ssh' + SSHPORT: 22 + NODE_ENV: 'development' + + web: + image: nginx + volumes: + - ./conf/nginx.template:/etc/nginx/conf.d/wetty.template + ports: + - '80:80' + environment: + - NGINX_DOMAIN=wetty.com + - NGINX_PORT=80 + - WETTY_HOST=wetty + - WETTY_PORT=3000 + command: >- + /bin/bash -c "envsubst + '$${NGINX_DOMAIN},$${NGINX_PORT},$${WETTY_HOST},$${WETTY_PORT}' < + /etc/nginx/conf.d/wetty.template > /etc/nginx/conf.d/default.conf && nginx + -g 'daemon off;'" + + wetty-ssh: + build: + context: . + dockerfile: containers/ssh/Dockerfile + image: wettyoss/wetty:ssh + +networks: + default: + name: wetty diff --git a/wetty/docs/API.md b/wetty/docs/API.md new file mode 100644 index 0000000..c40ff30 --- /dev/null +++ b/wetty/docs/API.md @@ -0,0 +1,90 @@ +## WeTTY + +Create WeTTY server + +- [WeTTy](#module_WeTTy) + - [start](#module_WeTTy..start) ⇒ `Promise` + - [connection](#event_connection) + - [spawn](#event_spawn) + - [exit](#event_exit) + - [disconnect](#event_disconnect) + - [server](#event_server) + +### WeTTy.start ⇒ `Promise` + +Starts WeTTY Server + +**Kind**: inner property of [`WeTTy`](#module_WeTTy) +**Returns**: `Promise` - Promise resolves once server is running + +| Param | Type | Default | Description | +| :------------------------ | --------- | ------------- | ---------------------------------------------------------------------------------------------------------------------- | +| [ssh] | `Object` | | SSH settings | +| [ssh.user] | `string` | `"''"` | default user for ssh | +| [ssh.host] | `string` | `"localhost"` | machine to ssh too | +| [ssh.auth] | `string` | `"password"` | authtype to use | +| [ssh.port] | `number` | `22` | port to connect to over ssh | +| [ssh.pass] | `string` | | Optional param of a password to use for ssh | +| [ssh.key] | `string` | | path to an optional client private key (connection will be password-less and insecure!) | +| [ssh.config] | `string` | | Specifies an alternative ssh configuration file. For further details see "-F" option in ssh(1) | +| [serverConf] | `Object` | | Server settings | +| [serverConf.base] | `Object` | `'/wetty/'` | Server settings | +| [serverConf.port] | `number` | `3000` | Port to run server on | +| [serverConf.host] | `string` | `'0.0.0.0'` | Host address for server | +| [serverConf.title] | `string` | `'WeTTY'` | Title of the server | +| [serverConf.bypasshelmet] | `boolean` | `false` | if helmet should be disabled on the sever | +| [command] | `string` | `"''"` | The command to execute. If running as root and no host specified this will be login if a host is specified will be ssh | +| [forcessh] | `boolean` | `false` | Connecting through ssh even if running as root | +| [ssl] | `Object` | | SSL settings | +| [ssl.key] | `string` | | Path to ssl key | +| [ssl.cert] | `string` | | Path to ssl cert | + +### "connection" + +**Kind**: event emitted by [`WeTTy`](#module_WeTTy) +**Properties** + +| Name | Type | Description | +| ---- | -------- | --------------------------- | +| msg | `string` | Message for logs | +| date | `Date` | date and time of connection | + +### "spawn" + +Terminal process spawned + +**Kind**: event emitted by [`WeTTy`](#module_WeTTy) +**Properties** + +| Name | Type | Description | +| ------- | -------- | -------------------------------------- | +| msg | `string` | Message containing pid info and status | +| pid | `number` | Pid of the terminal | +| address | `string` | address of connecting user | + +### "exit" + +Terminal process exits + +**Kind**: event emitted by [`WeTTy`](#module_WeTTy) +**Properties** + +| Name | Type | Description | +| ---- | -------- | -------------------------------------- | +| code | `number` | the exit code | +| msg | `string` | Message containing pid info and status | + +### "disconnect" + +**Kind**: event emitted by [`WeTTy`](#module_WeTTy) + +### "server" + +**Kind**: event emitted by [`WeTTy`](#module_WeTTy) +**Properties** + +| Name | Type | Description | +| ---------- | -------- | ------------------------------- | +| msg | `string` | Message for logging | +| port | `number` | port sever is on | +| connection | `string` | connection type for web traffic | diff --git a/wetty/docs/README.md b/wetty/docs/README.md new file mode 100644 index 0000000..f59996c --- /dev/null +++ b/wetty/docs/README.md @@ -0,0 +1,39 @@ +# Docs + +![WeTTY](./terminal.png?raw=true) + +- [AtoZ](./atoz.md) +- [Running as daemon](./service.md) +- [HTTPS Support](./https.md) + - [Using NGINX](./nginx.md) + - [Using Apache](./apache.md) +- [Automatic Login](./auto-login.md) +- [Downloading Files](./downloading-files.md) +- [Development Docs](./development.md) + +## API + +For WeTTY options and event details please refer to the [api docs](./API.md) + +### Getting started + +WeTTY is event driven. To Spawn a new server call `wetty.start()` with no +arguments. + +```javascript +import { start } from 'wetty'; + +start(/* server settings, see Options */) + .then((wetty) => { + console.log('server running'); + wetty + .on('exit', ({ code, msg }) => { + console.log(`Exit with code: ${code} ${msg}`); + }) + .on('spawn', (msg) => console.log(msg)); + /* code you want to execute */ + }) + .catch((err) => { + console.error(err); + }); +``` diff --git a/wetty/docs/apache.md b/wetty/docs/apache.md new file mode 100644 index 0000000..a9e6793 --- /dev/null +++ b/wetty/docs/apache.md @@ -0,0 +1,151 @@ +## Run WeTTY behind nginx or apache + +As said earlier you can use a proxy to add https to WeTTY. + +**Note** that if your proxy is configured for https you should run WeTTY without +SSL + +If your proxy uses a base path other than `/wetty`, specify the path with the +`--base` flag, or the `BASE` environment variable. + +The following confs assume you want to serve WeTTY on the url +`example.com/wetty` and are running WeTTY with the default base and serving it +on the same server + +Put the following configuration in apache's conf: + +```apache +RewriteCond %{REQUEST_URI} ^/wetty/socket.io [NC] +RewriteCond %{QUERY_STRING} transport=websocket [NC] +RewriteRule /wetty/socket.io/(.*) ws://localhost:3000/wetty/socket.io/$1 [P,L] + + + DirectorySlash On + Require all granted + ProxyPassMatch http://127.0.0.1:3000 + ProxyPassReverse /wetty/ + +``` + +## SAML2 integration to auth users + +This conf is using apache2 (as for nginx, SAML2 integration is not available on +the community version, only pro). + +Main idea is to propagate the SAML2 validated user identity into the +`remote-user` HTTP header. You need to have the user id returned within the +SAML2 NameID matching the username defined on the platform WeTTY is running. + +E.g: You can ask the Idp to return a sAMAccountName within the SAML2Response +NameID, and provision beforehand those allowed users on the OS WeTTY is running +on. + +### SAML2 Metadata generation + +SAML2 metadata needs to be generated for this new service on the server and +exchanged with the Idp. We will use the script provided at +https://raw.githubusercontent.com/bitsensor/saml-proxy/master/mellon_create_metadata.sh + +``` +$ mellon_create_metadata.sh urn:https://foo.bar.tlz https://foo.bar.tld/mellon +``` + +Then we move the generated files over `/etc/apache2/saml2/foo.{xml,key,cert}`. + +You need to put here additionally the metadata from your SAML2 provider, named +here `idp.xml` and exchange you foo.xml with it. + +### Apache2 conf + +```apache + + ServerName foo.bar.tld + ServerAdmin admin@bar.tld + + SSLEngine on + SSLCertificateFile /etc/apache2/ssl/foo.pem + SSLCertificateKeyFile /etc/apache2/ssl/foo.key + + RedirectMatch ^/$ /wetty/ + ProxyPass "/wetty" "http://127.0.0.1:3000/wetty" + + + AuthType Mellon + MellonEnable info + + # this propagates to apache2 (and thus to access log) the proper user id, and not + # the transient nameid that is taken by default + # it has no impact on the backend as we propagate identify via remote-user header there + MellonUser "NameID" + + MellonEndpointPath /mellon/ + MellonSPMetadataFile /etc/apache2/saml2/foo.xml + MellonSPPrivateKeyFile /etc/apache2/saml2/foo.key + MellonSPCertFile /etc/apache2/saml2/foo.cert + MellonIdPMetadataFile /etc/apache2/saml2/idp.xml + + # the identity propagated to WeTTY (as HTTP header 'remote-user: xxxxx') + # is retrieved from SAMLResponse NameID attribute + RequestHeader set remote-user %{MELLON_NAMEID}e + + + + AuthType Mellon + MellonEnable auth + Require valid-user + + + # security hazard for switching between users, disabled if remote-user set as recent github commit + # but not yet published via npm, so we put here a double security belt + + Deny from all + + +``` + +### Auto login + +If you want to have a seamless login by trusting your IdP for authentication, +you can create password-less users on the WeTTY platform and have them trust an +SSH key used by the NodeJS, owned by the dedicated WeTTY OS user. + +WeTTY instantiation with proper parameters, especially the SSH private key is +done via the following systemd service `/etc/systemd/system/wetty.service`: + +``` +[Unit] +Description=WeTTY Web Terminal +After=network.target + +[Service] +User=wetty +Type=simple +WorkingDirectory=/home/wetty/.node_modules/wetty/ +ExecStart=/usr/bin/node . -p 3000 --host 127.0.0.1 --ssh-key /home/wetty/.ssh/wetty --ssh-auth publickey --force-ssh --title "Foo bar terminal services" +TimeoutStopSec=20 +KillMode=mixed +Restart=always +RestartSec=2 + +[Install] +WantedBy=multi-user.target +``` + +For your new users to be automatically trusting this SSH key when provisioning, +you may add the pubkey to `/etc/skel/.ssh/authorized_keys`. + +### Security precautions + +You probably don't want local users to impersonate each other, for that you need +to make sure that: + +1. NodeJS is listening only to localhost: provided by `wetty.service` +2. **Only** the apache2 process can join the WeTTY port. Else local users will + be able to connect and forge a `remote-user` header: provided by + `iptables -A OUTPUT -o lo -p tcp --dport 3000 -m owner \! --uid-owner www-data -j DROP` +3. Validate your WeTTY version does not allow access to `/wetty/ssh/` else again + you will be able to impersonate anyone: provided by either: + 1. WeTTY version 2.0.3 and beyond implements this by disabling this feature + in case of `remote-user` presence + 2. apache2 conf as provided in previous section (containing the + ``) diff --git a/wetty/docs/assets/css/main.css b/wetty/docs/assets/css/main.css new file mode 100644 index 0000000..6ff704b --- /dev/null +++ b/wetty/docs/assets/css/main.css @@ -0,0 +1,3 @@ +:root { + --content-max-width: 100%; +} diff --git a/wetty/docs/assets/img/android-chrome-192x192.png b/wetty/docs/assets/img/android-chrome-192x192.png new file mode 100644 index 0000000..5133b3a Binary files /dev/null and b/wetty/docs/assets/img/android-chrome-192x192.png differ diff --git a/wetty/docs/assets/img/android-chrome-512x512.png b/wetty/docs/assets/img/android-chrome-512x512.png new file mode 100644 index 0000000..44257c3 Binary files /dev/null and b/wetty/docs/assets/img/android-chrome-512x512.png differ diff --git a/wetty/docs/assets/img/apple-touch-icon.png b/wetty/docs/assets/img/apple-touch-icon.png new file mode 100644 index 0000000..4d21439 Binary files /dev/null and b/wetty/docs/assets/img/apple-touch-icon.png differ diff --git a/wetty/docs/assets/img/favicon-16x16.png b/wetty/docs/assets/img/favicon-16x16.png new file mode 100644 index 0000000..fa8cc22 Binary files /dev/null and b/wetty/docs/assets/img/favicon-16x16.png differ diff --git a/wetty/docs/assets/img/favicon-32x32.png b/wetty/docs/assets/img/favicon-32x32.png new file mode 100644 index 0000000..3e67f36 Binary files /dev/null and b/wetty/docs/assets/img/favicon-32x32.png differ diff --git a/wetty/docs/assets/img/favicon.ico b/wetty/docs/assets/img/favicon.ico new file mode 100644 index 0000000..2ad860f Binary files /dev/null and b/wetty/docs/assets/img/favicon.ico differ diff --git a/wetty/docs/assets/img/site.webmanifest b/wetty/docs/assets/img/site.webmanifest new file mode 100644 index 0000000..fa99de7 --- /dev/null +++ b/wetty/docs/assets/img/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/wetty/docs/atoz.md b/wetty/docs/atoz.md new file mode 100644 index 0000000..821db49 --- /dev/null +++ b/wetty/docs/atoz.md @@ -0,0 +1,678 @@ +# Introduction + +This is an A to Z guide that will help you get WeTTY up and running on a Debian +based system. It covers the key configuration areas by using copy and paste +commands. This will help you install this application and get it securely up and +running with minimal system interference and reversible changes. It should also +provide enough information to allow you to understand and extend that +configuration for your personal requirements. + +**Note:** Some of these configurations are optional, such as self signed SSL and +public key authentication. The purpose of the guide is to show you how to +correctly understand, configure, install and use these options should you wish +to use them but they are not required to use WeTTY in general. + +## Required dependencies + +`Node` - WeTTY requires node v20 or greater. We will install this locally for a +non root user later in the guide. + +`python` - This should be installed by default but we will include it in our +`apt-get` command to be safe. + +`build-essential` - We need this specifically for `node-gyp` to build packages +when using `npm` to install packages. + +As the `root` or `sudo` user run these commands: + +```bash +sudo apt update +sudo apt install -y build-essential curl python +``` + +If you have no root access and just want to check the dependencies are installed +you can use these commands: + +```bash +dpkg -s python | grep Status: +dpkg -s build-essential | grep Status: +``` + +If the package is installed you will see this result: + +```bash +Status: install ok installed +``` + +## Create a local user account + +For this guide, unless specifically stated, you should not use a `root` account +to install and run WeTTY. Please use an existing local account or create one +now. + +**Note:** Whichever user runs WeTTY should be the same user you wish to +authenticate with via `ssh` to keep this guide simple. + +If you need to create a local user account you can run this command: + +**Important note:** replace `username` with a user name of your choosing and +create a password when prompted + +```bash +adduser --gecos "" username +``` + +Switch to your local user now and open an `ssh` session to continue with this +guide. + +## Install node locally + +To install and manage `node` as a local user we are going to use +[Node Version Manager](https://github.com/nvm-sh/nvm). This is an established +solution for installing and managing multiple versions of node without needing +`root` access. This will allow you to install and use multiple versions of +`node` at the same time. + +This command will download and install `nvm` and reload our shell. + +```bash +curl -sL https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash && source ~/.profile +``` + +This command will install the latest version of the v20 branch, which is the +minimum required version for WeTTY. + +```bash +nvm install 20 +``` + +You can now call `node` to check it works using this command. + +```bash +node -v +``` + +Your result should look something like this. + +```bash +v20.2.0 +``` + +**Note:** There is an important consideration with the `nvm` method. `node` is +only in the local user's path through sourcing of the `~/.nvm/nvm.sh` which is +done when the user logs in and the shell sources the user's `.bashrc` file. So +for some applications who are not aware of this local shell environment `node` +will not be usable unless we provide a full path and `nvm` commands will also be +unavailable. The way we over come this issue for the needs of this guide is by +using this command substitution to provide the full path, where applicable: + +```bash +$(source ~/.nvm/nvm.sh && nvm which 20) +``` + +**Why?** This command will always provide us with the path to the most current +version of `node 20` installed via `nvm` regardless of other versions of `node` +installed with `nvm`. + +## Generate OpenSSL certificates + +**Why?** So that later we can configure WeTTY to work with `https` and make sure +we interact with WeTTY over a secure connection at all times. + +Make the required directory using this command: + +```bash +mkdir -p ~/.ssl +``` + +Generate the self signed `openssl` certificates we will use to encrypt our web +traffic when using WeTTY using this command: + +**Note:** we are using `ecdsa` using the `secp384r1` curve. Tested to be +compatible with Chrome and Firefox browsers. + +```bash +openssl req -x509 -nodes -days 1095 -newkey ec:<(openssl ecparam -name secp384r1) -subj "/C=GB/ST=None/L=None/O=None/OU=None/CN=None" -out ~/.ssl/wetty.crt -keyout ~/.ssl/wetty.key +``` + +Now give these file and folders the correct permissions using these commands: + +```bash +chmod 700 ~/.ssl +chmod 644 ~/.ssl/wetty.crt +chmod 600 ~/.ssl/wetty.key +``` + +This is all we need to do for now in regards to https. + +## Generate the ssh key file + +**Why?** So that later we can set up automatic login via `ssh`. Our instance +will authorize using this key file stored locally. + +Make the required directory, if it does not exist, using this command: + +```bash +mkdir -p ~/.ssh +``` + +Create the `ssh` private key using `ed25519` that we need to authorize our local +connection, using this command: + +```bash +ssh-keygen -q -C "wetty-keyfile" -t ed25519 -N '' -f ~/.ssh/wetty 2>/dev/null <<< y >/dev/null +``` + +**Important Note:** You must add the public key to your `authorized_keys` file +in order to be able to log in using your `ssh` key file when accessing WeTTY via +a web browser. + +Copy the key to our `~/.ssh/authorized_keys` file, using this command: + +```bash +cat ~/.ssh/wetty.pub >> ~/.ssh/authorized_keys +``` + +Now give these file and folders the correct permissions, using these commands: + +```bash +chmod 700 ~/.ssh +chmod 644 ~/.ssh/authorized_keys +chmod 600 ~/.ssh/wetty +``` + +**Optional:** A housekeeping command. If you need to remove all entries of the +WeTTY public key with the comment `wetty-keyfile` from the +`~/.ssh/authorized_keys` file use this command. Otherwise ignore this. + +```bash +sed -r '/^ssh-ed25519(.*)wetty-keyfile$/d' -i ~/.ssh/authorized_keys +``` + +## Install WeTTY + +**Note:** we are using `-g` for `npm` along with `--prefix ~/` so that the +application's symbolic link is installed to our `~/bin` directory and available +in our local user's `PATH`. + +As your local user run these commands: + +To make sure the local user's `~/bin` directory exists and is in the `PATH` +please run the following command. + +```bash +mkdir -p ~/bin && source ~/.profile +``` + +Then use `npm` to install `wetty`. + +```bash +npm -g i wetty --prefix ~/ +``` + +Once successfully installed the application should be available in your local +user's `PATH`. To test the installation was successful please use this command: + +```bash +wetty -h +``` + +## Accessing the web interface via our external IP + +If you are using your external IP and not a domain to access WeTTY this step +needs to be done here because it is not easy to do in the next steps if WeTTY is +running in the terminal. + +This command will generate the correct URL you need to visit after using the +start up commands in the following section. + +```bash +echo https://$(curl -s4 icanhazip.com):3000 +``` + +_Please make a note of this URL now._ + +## Running WeTTY + +Now we have all the ground work done we can focus on our WeTTY server +configuration settings. + +For example, the below command would provide a `https` instance with automatic +`ssh` authorization using our `wetty` private key on port `3000` accessible at +`https://IP:3000` . + +**Important note:** This command will run in your current terminal session and +not in the background. The key combination of `CTRL` + `c` will exit the +application. + +```bash +wetty --host 0.0.0.0 --port 3000 --title wetty --base / --ssh-key ~/.ssh/wetty --ssh-host localhost --ssh-user $(whoami) --ssh-port 22 --ssh-auth publickey --ssl-key ~/.ssl/wetty.key --ssl-cert ~/.ssl/wetty.crt +``` + +Since you may not need all these settings we will look through what each one +does below so that you can decide how to best configure your instance. + +### Environment settings explained + +Let's break it down so that we can understand what's being done and why. + +```bash +--host 0.0.0.0 --port 3000 --title wetty --base / +``` + +`--host 0.0.0.0` - defines the interface we want to bind to. Using `0.0.0.0` +means that we bind to all available interfaces so using this setting just works. +When we use nginx we can change this to `--host 127.0.0.1` in order to prevent +generic port access to the application and force traffic through our nginx +reverse proxy URL. + +`--port 3000` - defines the local listening port. You will use this port to +connect via the remotely accessible web server or when configuring a reverse +proxy through nginx. + +`--title wetty` - an optional setting to set the window title for this `wetty` +session. + +`--base /` - changes the default base URL setting from `/wetty/` to define the +remote URL. We use `--base /` to make `wetty` accessible on the URL format +`https://IP:3000` instead of `https://IP:3000/wetty` but we would change this +back if we use nginx to reverse proxy the application. + +### SSH settings explained + +These settings are all specific to `ssh` and will enable you to automatically +log into you `ssh` session for the selected user. + +```bash +--ssh-key ~/.ssh/wetty --ssh-host localhost --ssh-user $(whoami) --ssh-port 22 --ssh-auth publickey +``` + +`--ssh-key ~/.ssh/wetty` - we are telling WeTTY to load our `ssh` key file that +we generated earlier. + +`--ssh-host localhost` - optional setting telling WeTTY to connect the host +`localhost` + +`--ssh-user $(whoami)` - defines our `ssh` username. In this case via the +command substitution of `whoami` which will not require your input of a +username. + +`--ssh-port 22` - optional setting to set the `ssh` port we need to connect to. + +`--ssh-auth publickey` defines the accepted authentication types. You do not +have to use the key file and you can instead require a password but setting this +to `--sshauth password`. You can specify both `--sshauth publickey,password` + +`--ssh-config configfile` - (not used for this guide) alternative ssh +configuration file. From ssh(1): + +> If a configuration file is given on the command line, the system-wide +> configuration file (/etc/ssh/ssh_config) will be ignored. The default for the +> per-user configuration file is ~/.ssh/config. + +### SSL settings explained + +These settings are specific to `openssl` to make WeTTY load https webserver so +that all data is transmitted over a secure connection. + +```bash +--ssl-key ~/.ssl/wetty.key --ssl-cert ~/.ssl/wetty.crt +``` + +`--ssl-key ~/.ssl/wetty.key` - tells WeTTY to load our `openssl` generated key +file. + +`--ssl-cert ~/.ssl/wetty.crt` - tells WeTTY to load our `openssl` generates +certificate file. + +### Optional - load settings via a configuration file + +As of WeTTY v2 there is official support for a configuration file used with the +flag `--conf` to specify the location of this file. + +Create the directory where we will store this configuration file. + +```bash +mkdir -p ~/.config/wetty +``` + +Use `nano` to open a file for editing. + +```bash +nano ~/.config/wetty/config.json +``` + +Here is the template `config.json` you need to use. + +**Note:** To be [validated json](https://codebeautify.org/jsonvalidator) the +below json example should have the `// ...` comments removed. With all comments +removed the example is valid json. They are in the example to help explain the +options and won't stop WeTTY from loading if you leave them in place. Lines you +do not need can be commented out but should be removed if you want the json to +pass validation. + +```json + "ssh": { + "user": "username", // default user to use when ssh-ing + "host": "localhost", // Server to ssh to + "auth": "publickey,password", // shh authentication, method. Defaults to "password", you can use "publickey,password" instead' + "pass": "password", // Password to use when ssh-ing + "key": "/home/username/.ssh/wetty", // path to an optional client private key, connection will be password-less and insecure! + "port": 22, // Port to ssh to + "knownHosts": "/dev/null" // ssh knownHosts file to use + }, + "server": { + "base": "/wetty/", // URL base to serve resources from + "port": 3000, // Port to listen on + "host": "0.0.0.0", // listen on all interfaces or can be 127.0.0.1 with nginx + "title": "WeTTY - The Web Terminal Emulator", // Page title + "bypassHelmet": false // Disable Helmet security checks + }, + "forceSSH": false, // Force sshing to local machine over login if running as root + "command": "login", // Command to run on server. Login will use ssh if connecting to different server + "ssl": { + "key": "/home/username/.ssl/wetty.key", + "cert": "/home/username/.ssl/wetty.crt" + } +} +``` + +Press `ctrl` + `x` and then press `y` to save then press `enter` to confirm and +exit `nano`. + +## System Environment Variables + +**Note:** We will not be using this section to configure WeTTY. We are simply +documenting it. + +There are some environment variables you can export that can be used by WeTTY to +configure an instance. + +```bash +BASE +PORT +TITLE +SSHUSER +SSHHOST +SSHAUTH +SSHPASS +SSHKEY +SSHPORT +KNOWNHOSTS +FORCESSH +COMMAND +ALLOWIFRAME +``` + +These can be used in the following way + +```bash +export PORT=3000 +``` + +There are currently no environment settings for variables not listed above. + +## Systemd service settings + +We will use a local user `systemd` service file to manage the `wetty` service. + +First, create the required directory, if it does not exist. + +```bash +mkdir -p ~/.config/systemd/user +``` + +### Systemd service + +Here is an example template of how to use service file with hardcoded values you +can set in the `wetty.service` file with all options enabled. + +Use `nano` to open a file for editing. + +```bash +nano ~/.config/systemd/user/wetty.service +``` + +Then copy and paste this code. + +**Note:** This is an example service file based on all the options documented +and configured so far. You may not want all these option enabled so please +remove or modify the `ExecStart` command based on your needs. + +```bash +[Unit] +Description=WeTTY +After=network-online.target + +[Service] +Type=simple +ExecStart=/bin/bash -c "$$(source /home/$$(whoami)/.nvm/nvm.sh && nvm which 12) /home/$$(whoami)/bin/wetty --host 0.0.0.0 -p 3000 --title wetty --base / --ssh-key /home/$$(whoami)/.ssh/wetty --ssh-host localhost --ssh-user $$(whoami) --ssh-port 22 --ssh-auth publickey --ssl-key /home/$$(whoami)/.ssl/wetty.key --ssl-cert /home/$$(whoami)/.ssl/wetty.crt" +Restart=always +RestartSec=2 +TimeoutStopSec=5 +SyslogIdentifier=wetty + +[Install] +WantedBy=default.target +``` + +Press `ctrl` + `x` and then press `y` to save then press `enter` to confirm and +exit `nano`. + +### Optional - Systemd service with config file + +Here is the example using our pseudo configuration file. All modifications to +the start up of `wetty` will be done by editing the `~/.config/Wetty/config` +file and then reloading the `wetty.service`. + +Use `nano` to open the file for editing. + +```bash +nano ~/.config/systemd/user/wetty.service +``` + +Then copy and paste this code. + +**Note:** This `ExecStart` assumes the location of your `config.json` to be +`~/.config/wetty/config.json`. Please make sure you use the correct location for +this file. + +```bash +[Unit] +Description=WeTTY +After=network-online.target + +[Service] +Type=simple +ExecStart=/bin/bash -c "$$(source /home/$$(whoami)/.nvm/nvm.sh && nvm which 20) /home/$$(whoami)/bin/wetty --conf /home/$$(whoami)/.config/wetty/config.json" +Restart=always +RestartSec=2 +TimeoutStopSec=5 +SyslogIdentifier=wetty + +[Install] +WantedBy=default.target +``` + +Press `ctrl` + `x` and then press `y` to save then press `enter` to confirm and +exit `nano`. + +### Activating your service + +Then you can enable and start your service. + +```bash +systemctl --user enable --now wetty +``` + +### Managing your services + +These commands will help you manage your service. + +```bash +systemctl --user daemon-reload +systemctl --user status wetty +systemctl --user start wetty +systemctl --user stop wetty +systemctl --user restart wetty +systemctl --user disable --now wetty +systemctl --user enable --now wetty +``` + +## Nginx reverse proxy + +If you want to use nginx as a reverse proxy here is the configuration file you +can use. + +Please modify these specific environment settings: + +**Why?** This will disable generic port access to the application and force +traffic via the nginx reverse proxy. + +```bash +--host 127.0.0.1 +``` + +**Why?** This change is so that our application does not attempt to load as the +web root of `/` for nginx. + +```bash +--base /wetty/ +``` + +Now you can use this nginx configuration file. + +**Note:** we are using `https` with `https://127.0.0.1:3000/wetty;` because we +configured `wetty` to run via `https` using our self signed ssl certificates. If +you chose not to run WeTTY with a self signed certificate you should changes +this to `http://127.0.0.1:3000/wetty;` + +Then copy and paste this into the `https` server block of your enable server +configuration file. + +```nginx +location /wetty { + proxy_pass https://127.0.0.1:3000/wetty; + # + proxy_pass_request_headers on; + # + proxy_set_header Host $host; + # + proxy_http_version 1.1; + # + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-NginX-Proxy true; + # + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $http_connection; + proxy_read_timeout 43200000; + proxy_set_header X-Forwarded-Ssl on; + # + proxy_redirect off; + proxy_buffering off; +} +``` + +Press `ctrl` + `x` and then press `y` to save then press `enter` to confirm and +exit `nano` + +Now you would need to reload nginx service using this command: + +```bash +systemctl restart nginx +``` + +### Accessing the web interface via nginx + +Visit the URL format `https://YourIPorDomain/wetty` and you can access WeTTY. + +This command will generate the correct URL you need to visit it you are not +using a domain. + +```bash +echo https://$(curl -s4 icanhazip.com)/wetty +``` + +## Protecting your instance of WeTTY + +**Disclaimer:** It is not recommended by this guide that you run an instance of +WeTTY on your server with no access control in place. + +If you chose to not use a password to login in you should protect your instance +behind either: + +1: +[Nginx basic auth](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/) + +2: [Authelia](https://github.com/authelia/authelia) + +## Configuration reference + +`wetty -h` configuration options for reference. + +```bash + --help, -h Print help message [boolean] + --version Show version number [boolean] + --conf config file to load config from [string] + --ssl-key path to SSL key [string] + --ssl-cert path to SSL certificate [string] + --ssh-host ssh server host [string] [default: "localhost"] + --ssh-port ssh server port [number] [default: 22] + --ssh-user ssh user [string] [default: ""] + --title window title [string] [default: "WeTTY - The Web Terminal Emulator"] + --ssh-auth defaults to "password", you can use "publickey,password" + instead [string] [default: "password"] + --ssh-pass ssh password [string] + --ssh-key path to an optional client private key (connection will be + password-less and insecure!) [string] + --ssh-config Specifies an alternative ssh configuration file. For further + details see "-F" option in ssh(1) [string] [default: ""] + --force-ssh Connecting through ssh even if running as root [boolean] [default: false] + --known-hosts path to known hosts file [string] + --base, -b base path to wetty [string] [default: "/wetty/"] + --port, -p wetty listen port [number] [default: 3000] + --host wetty listen host [string] [default: "0.0.0.0"] + --command, -c command to run in shell [string] [default: "login"] + --allow-iframe Allow wetty to be embedded in an iframe, defaults to allowing + same origin [boolean] [default: false] +``` + +## Updating WeTTY + +```bash +npm -g update wetty --prefix ~/ +``` + +To update or downgrade to a specific version you use this command: + +```bash +npm -g i wetty@2.7.0 --prefix ~/ +``` + +Now restart your `wetty` service. + +## Updating nvm + +The proper way to update NVM is to use git. The `.nvm` directory is a git repo. + +These commands will update NVM to the latest version of the script and load it +to your shell. + +```bash +cd ~/.nvm +git fetch --tags +git checkout $(git describe --abbrev=0 --tags --match "v[0-9]*" $(git rev-list --tags --max-count=1)) +source ~/.nvm/nvm.sh +``` + +## Updating node + +You can use the same command you used to install it with `nvm` + +```bash +nvm install 20 +``` diff --git a/wetty/docs/auto-login.md b/wetty/docs/auto-login.md new file mode 100644 index 0000000..977b68e --- /dev/null +++ b/wetty/docs/auto-login.md @@ -0,0 +1,22 @@ +# Auto Login + +WeTTY Supports a form of auto login by passing a users password though url +params. + +This is not a required feature and the security implications for passing the +password in the url will have to be considered by the user. + +## Requirements + +For auto-login feature you'll need sshpass installed + +- `apt-get install sshpass` (debian eg. Ubuntu) +- `yum install sshpass` (red hat flavours eg. CentOs) + +## Usage + +You can also pass the ssh password as an optional query parameter to auto-login +the user like this (Only while running WeTTY as a non root account or when +specifying the ssh host): + +`http://yourserver:3000/wetty/ssh/?pass=` diff --git a/wetty/docs/development.md b/wetty/docs/development.md new file mode 100644 index 0000000..ae96172 --- /dev/null +++ b/wetty/docs/development.md @@ -0,0 +1,25 @@ +# Installation from Source + +WeTTY can be installed from source or from npm. + +To install from source run: + +```bash +$ git clone https://github.com/butlerx/wetty.git +$ cd wetty +$ pnpm install +$ pnpm build +``` + +## Development Env + +To run WeTTY in dev mode you can run `pnpm dev`. + +WeTTY will then be served from `http://localhost:3000/wetty` on your machine. + +The server will be using the [`conf/config.json5`](../conf/config.json5) config +file and be pointing at `localhost` on port `22` . + +The Dev server will rebuild WeTTY when ever a file is edited and restart the +server with the new build. Any current ssh session in WeTTY will be killed and +the user logged out. diff --git a/wetty/docs/docker.md b/wetty/docs/docker.md new file mode 100644 index 0000000..fe86428 --- /dev/null +++ b/wetty/docs/docker.md @@ -0,0 +1,27 @@ +# Dockerized Version + +WeTTY can be run from a container to ssh to a remote host or the host system. +This is handy for quick deployments. Just modify `docker-compose.yml` for your +host and run: + +```sh +$ docker-compose up -d +``` + +This will start 2 containers, one will be WeTTY container running ssh client the +other will be a container running ssh server. + +Visit the appropriate URL in your browser +(`[localhost|$(boot2docker ip)]:PORT`). + +The default username is `term` and the password is `term`, if you did not modify +`SSHHOST` + +In the docker version all flags can be accessed as environment variables such as +`SSHHOST` or `SSHPORT`. + +If you don't want to build the image yourself just remove the line `build; .` + +If you wish to use the WeTTY container in prod just modify the WeTTY container +to have `SSHHOST` point to the server you want to ssh to and remove the ssh +server container. diff --git a/wetty/docs/downloading-files.md b/wetty/docs/downloading-files.md new file mode 100644 index 0000000..3c2fc15 --- /dev/null +++ b/wetty/docs/downloading-files.md @@ -0,0 +1,44 @@ +# File Downloading + +WeTTY supports file downloads by printing terminal escape sequences between a +base64 encoded file. The name of the downloaded file can optionally be provided, +also base64 encoded, before the encoded file contents with a `:` separating them. + +The terminal escape sequences used are `^[[5i` and `^[[4i` (VT100 for "enter +auto print" and "exit auto print" respectively - +https://vt100.net/docs/tp83/appendixc.html). + +To take advantage add the following bash function to your `.bashrc` + +```bash +function wetty-download() { + file=${1:-/dev/stdin} + + nameprefix="" + if [[ -f "$file" ]]; then + nameprefix="$(basename "$file" | base64 -w 0):" + fi + + + if [[ -f "$file" || "$file" == "/dev/stdin" ]]; then + printf "\033[5i"$nameprefix$(cat "$file" | base64 -w 0)"\033[4i" + else + echo "$file does not appear to be a file" + fi +} +``` + +You are then able to download files via WeTTY! + +```bash +wetty-download my-pdf-file.pdf +``` + +or you can still use the classic style: + +```bash +$ cat my-pdf-file.pdf | wetty-download +``` + +WeTTY will then issue a popup like the following that links to a local file +blob: `Download ready: file-20191015233654.pdf` diff --git a/wetty/docs/flags.md b/wetty/docs/flags.md new file mode 100644 index 0000000..6aad58d --- /dev/null +++ b/wetty/docs/flags.md @@ -0,0 +1,38 @@ +# Flags + +WeTTY can be run with the `--help` flag to get a full list of flags. + +## Server Port + +WeTTY runs on port `3000` by default. You can change the default port by +starting with the `--port` or `-p` flag. + +## SSH Host + +If WeTTY is run as root while the host is set as the local machine it will use +the `login` binary rather than ssh. If no host is specified it will use +`localhost` as the ssh host. + +If instead you wish to connect to a remote host you can specify the host with +the `--ssh-host` flag and pass the IP or DNS address of the host you want to +connect to. + +## Default User + +You can specify the default user used to ssh to a host using the `--ssh-user`. +This user can overwritten by going to +`http://yourserver:3000/wetty/ssh/`. If this is left blank a user will +be prompted to enter their username when they connect. + +## SSH Port + +By default WeTTY will try to ssh to port `22`, if your host uses an alternative +ssh port this can be specified with the flag `--ssh-port`. + +## WeTTY URL + +If you'd prefer an HTTP base prefix other than `/wetty`, you can specify that +with `--base`. + +**Do not set this to `/ssh/${something}`, as this will break username matching +code.** diff --git a/wetty/docs/https.md b/wetty/docs/https.md new file mode 100644 index 0000000..45b5f39 --- /dev/null +++ b/wetty/docs/https.md @@ -0,0 +1,21 @@ +# HTTPS + +Always use HTTPS especially with a terminal to your server. You can add HTTPS by +either using WeTTY behind a proxy or directly. + +See docs for [NGinX](./nginx.md) and [Apache](./apache.md) for running behind a +proxy. + +To run WeTTY directly with SSL use both the `--ssl-key` and `--ssl-cert` flags +and pass them the path too your cert and key as follows: + +```bash +wetty --ssl-key key.pem --ssl-cert cert.pem +``` + +If you don't have SSL certificates from a CA you can create a self signed +certificate using this command: + +```bash +openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 30000 -nodes +``` diff --git a/wetty/docs/index.html b/wetty/docs/index.html new file mode 100644 index 0000000..d8817e8 --- /dev/null +++ b/wetty/docs/index.html @@ -0,0 +1,96 @@ + + + + + + + + WeTTY = Web + TTY + + + + + + + + + + + + + + + +
Please wait...
+ + + + + + + + + + diff --git a/wetty/docs/nginx.md b/wetty/docs/nginx.md new file mode 100644 index 0000000..c8c6ab9 --- /dev/null +++ b/wetty/docs/nginx.md @@ -0,0 +1,34 @@ +## Run WeTTY behind nginx + +As said earlier you can use Nginx to add https to WeTTY. + +**Note** that if your proxy is configured for https you should run WeTTY without +SSL + +If you configure nginx to use a base path other than `/wetty`, then specify that +path with the `--base` flag, or the `BASE` environment variable. + +The following confs assume you want to serve WeTTY on the url +`example.com/wetty` and are running WeTTY with the default base and serving it +on the same server + +For a more detailed look see the +[nginx.conf](https://github.com/butlerx/wetty/blob/main/conf/nginx.template) +used for testing + +Put the following configuration in your nginx conf: + +```nginx +location ^~ /wetty { + proxy_pass http://127.0.0.1:3000/wetty; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 43200000; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; +} +``` diff --git a/wetty/docs/service.md b/wetty/docs/service.md new file mode 100644 index 0000000..815c1bb --- /dev/null +++ b/wetty/docs/service.md @@ -0,0 +1,32 @@ +## Run WeTTY as a service daemon + +WeTTY can be run as a daemon on your service init confs and systemd services are +bundled with the npm package to make this easier. + +### init.d + +```bash +$ npm -g i wetty +$ sudo cp ~/.node_modules/wetty/conf/wetty.conf /etc/init +$ sudo start wetty +``` + +### systemd + +```bash +$ yarn global add wetty +$ cp ~/.node_modules/wetty/conf/wetty.service ~/.config/systemd/user/ +$ systemctl --user enable wetty +$ systemctl --user start wetty +``` + +This will start WeTTY on port 3000. If you want to change the port or redirect +stdout/stderr you should change the last line in `wetty.conf` file, something +like this: + +```systemd +exec sudo -u root wetty -p 80 >> /var/log/wetty.log 2>&1 +``` + +Systemd requires an absolute path for a unit's WorkingDirectory, consequently +`$HOME` will need updating to an absolute path in the `wetty.service` file. diff --git a/wetty/docs/sidebar.md b/wetty/docs/sidebar.md new file mode 100644 index 0000000..91ac827 --- /dev/null +++ b/wetty/docs/sidebar.md @@ -0,0 +1,11 @@ +- [Home](README.md) +- [Apache](apache.md) +- [API](API.md) +- [AtoZ](atoz.md) +- [auto-login](auto-login.md) +- [development](development.md) +- [downloading-files](downloading-files.md) +- [flags](flags.md) +- [https](https.md) +- [nginx](nginx.md) +- [service](service.md) diff --git a/wetty/docs/terminal.png b/wetty/docs/terminal.png new file mode 100644 index 0000000..4b51c85 Binary files /dev/null and b/wetty/docs/terminal.png differ diff --git a/wetty/index.html b/wetty/index.html new file mode 100644 index 0000000..07a7a46 --- /dev/null +++ b/wetty/index.html @@ -0,0 +1,52 @@ + + + + + + + + WeTTY - Gateway Client + + +
+
+
+ +
+
+ +
+ + +
+ +
+ +
+
Up
+
Down
+
Left
+
Right
+
Esc
+
Ctrl
+
Tab
+
+
+ +
+ + + diff --git a/wetty/package.json b/wetty/package.json new file mode 100644 index 0000000..e14c205 --- /dev/null +++ b/wetty/package.json @@ -0,0 +1,186 @@ +{ + "name": "wetty", + "version": "2.7.0", + "description": "WeTTY = Web + TTY. Terminal access in browser over http/https", + "homepage": "https://github.com/butlerx/wetty", + "license": "MIT", + "type": "module", + "bin": "./build/main.js", + "main": "./build/main.js", + "exports": "./build/server.js", + "files": [ + "build/", + "conf/" + ], + "scripts": { + "build": "node build.js", + "clean": "rm -rf build", + "contributor": "all-contributors", + "dev": "vite", + "dev:wetty": "NODE_ENV=development concurrently --kill-others --raw --success first \"pnpm build --watch\" \"nodemon -w build -i build/client -w conf/config.json5 --delay 200ms . -- --conf conf/config.json5\"", + "docker-compose-entrypoint": "ssh-keyscan -H wetty-ssh >> ~/.ssh/known_hosts; pnpm start", + "lint": "eslint src", + "lint:fix": "eslint --fix src", + "start": "NODE_ENV=production node .", + "test": "mocha", + "prepare": "husky install" + }, + "repository": "git://github.com/butlerx/wetty.git", + "author": "Cian Butler (cianbutler.ie)", + "bugs": { + "url": "https://github.com/butlerx/wetty/issues" + }, + "lint-staged": { + "*.{js,ts}": [ + "eslint --fix" + ], + "*.{json,scss,md}": [ + "prettier --write" + ] + }, + "engines": { + "node": ">=18.0.0" + }, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.1.2", + "@fortawesome/free-solid-svg-icons": "^6.1.2", + "@xterm/xterm": "^5.2.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/addon-image": "^0.8.0", + "@xterm/addon-web-links": "^0.11.0", + "compression": "^1.7.4", + "etag": "^1.8.1", + "express": "^4.17.1", + "express-winston": "^4.0.5", + "file-type": "^12.3.0", + "find-up": "^5.0.0", + "fresh": "^0.5.2", + "fs-extra": "^9.0.1", + "gc-stats": "^1.4.0", + "helmet": "^4.1.0", + "json5": "^2.1.3", + "lodash": "^4.17.20", + "node-gyp": "^9.1.0", + "node-pty": "^0.10.0", + "parseurl": "^1.3.3", + "prom-client": "^14.0.1", + "response-time": "^2.3.2", + "sass": "^1.54.4", + "serve-static": "^1.15.0", + "socket.io": "^4.5.1", + "socket.io-client": "^4.5.1", + "toastify-js": "^1.9.1", + "url-value-parser": "^2.1.0", + "winston": "^3.3.3", + "yargs": "^17.7.2" + }, + "devDependencies": { + "@snowpack/plugin-run-script": "^2.3.0", + "@types/chai": "^4.3.1", + "@types/compression": "^1.7.0", + "@types/etag": "^1.8.0", + "@types/express": "^4.17.8", + "@types/fresh": "^0.5.0", + "@types/fs-extra": "^9.0.1", + "@types/gc-stats": "^1", + "@types/helmet": "^0.0.48", + "@types/jsdom": "^12.2.4", + "@types/lodash": "^4.14.161", + "@types/mocha": "^9.1.1", + "@types/morgan": "^1.7.37", + "@types/node": "^20.2.6", + "@types/parseurl": "^1.3.1", + "@types/response-time": "^2", + "@types/serve-static": "^1.15.3", + "@types/sinon": "^10.0.13", + "@types/toastify-js": "^1.9.2", + "@types/yargs": "^17.0.24", + "@typescript-eslint/eslint-plugin": "^5.59.9", + "@typescript-eslint/parser": "^5.59.9", + "all-contributors-cli": "^6.17.2", + "chai": "^4.3.6", + "concurrently": "^8.2.2", + "esbuild": "^0.21.5", + "esbuild-plugin-copy": "^2.1.1", + "esbuild-sass-plugin": "^3.3.1", + "eslint": "^8.36.0", + "eslint-config-airbnb-base": "latest", + "eslint-config-prettier": "^8.6.0", + "eslint-import-resolver-typescript": "^3.4.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-mocha": "^10.1.0", + "eslint-plugin-prettier": "^4.2.1", + "git-authors-cli": "^1.0.42", + "husky": "^9.0.11", + "jsdom": "^16.5.0", + "lint-staged": "^13.2.2", + "mocha": "^10.0.0", + "nodemon": "^3.1.4", + "prettier": "^2.5.1", + "sinon": "^14.0.0", + "ts-node": "^10.9.1", + "typescript": "^5.1.3", + "vite": "^6.2.6" + }, + "contributors": [ + "butlerx ", + "Krishna Srinivas ", + "userdocs <16525024+userdocs@users.noreply.github.com>", + "Boyan Rabchev ", + "Ben Letchford ", + "Antonio Calatrava ", + "Strubbl ", + "Oleg Kurapov ", + "Anthony Jund ", + "Kyle Lucy ", + "Luca Milanesio ", + "nosemeocurrenada ", + "cbutler ", + "Henri ", + "Imuli ", + "Janos Kasza ", + "mirtouf ", + "Koushik M.L.N ", + "Denis Kramer ", + "Harrison Pace ", + "Jarrett Gilliam ", + "Nathan LeClaire ", + "SouraDutta <33066261+SouraDutta@users.noreply.github.com>", + "Aayush Garg-gamer1478 <74775129+gamer-12748@users.noreply.github.com>", + "Bertrand Roussel ", + "Christian7573 ", + "Dean Shub ", + "Dmytri Kleiner ", + "Felix Bartels ", + "Felix Pojtinger ", + "Josua Frank ", + "Georgelemental ", + "Loz Brown ", + "Grant Handy ", + "harryleesan ", + "Andreas Kloeckner ", + "James Turnbull ", + "Arturo R ", + "Josh Samuelson ", + "Jurre Vriesen ", + "Kevin ", + "Farhan Khan ", + "Kasper Holbek Jensen ", + "Krzysztof Suszyński ", + "justluk ", + "Mathieu Geli ", + "Mihir Kumar ", + "Neale Pickett ", + "pablo-zarate ", + "Matthew Piercey ", + "Alex Cline ", + "Robert ", + "Sergei Ratnikov ", + "Shimi ", + "Sven Fischer ", + "Taha ", + "Tri Nguyen ", + "Vamshi K Ponnapalli " + ], + "packageManager": "pnpm@9.4.0+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a" +} diff --git a/wetty/pnpm-lock.yaml b/wetty/pnpm-lock.yaml new file mode 100644 index 0000000..3164547 --- /dev/null +++ b/wetty/pnpm-lock.yaml @@ -0,0 +1,7261 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@fortawesome/fontawesome-svg-core': + specifier: ^6.1.2 + version: 6.5.2 + '@fortawesome/free-solid-svg-icons': + specifier: ^6.1.2 + version: 6.5.2 + '@xterm/addon-fit': + specifier: ^0.10.0 + version: 0.10.0(@xterm/xterm@5.5.0) + '@xterm/addon-image': + specifier: ^0.8.0 + version: 0.8.0(@xterm/xterm@5.5.0) + '@xterm/addon-web-links': + specifier: ^0.11.0 + version: 0.11.0(@xterm/xterm@5.5.0) + '@xterm/xterm': + specifier: ^5.2.0 + version: 5.5.0 + compression: + specifier: ^1.7.4 + version: 1.7.4 + etag: + specifier: ^1.8.1 + version: 1.8.1 + express: + specifier: ^4.17.1 + version: 4.19.2 + express-winston: + specifier: ^4.0.5 + version: 4.2.0(winston@3.13.0) + file-type: + specifier: ^12.3.0 + version: 12.4.2 + find-up: + specifier: ^5.0.0 + version: 5.0.0 + fresh: + specifier: ^0.5.2 + version: 0.5.2 + fs-extra: + specifier: ^9.0.1 + version: 9.1.0 + gc-stats: + specifier: ^1.4.0 + version: 1.4.1 + helmet: + specifier: ^4.1.0 + version: 4.6.0 + json5: + specifier: ^2.1.3 + version: 2.2.3 + lodash: + specifier: ^4.17.20 + version: 4.17.21 + node-gyp: + specifier: ^9.1.0 + version: 9.4.1 + node-pty: + specifier: ^0.10.0 + version: 0.10.1 + parseurl: + specifier: ^1.3.3 + version: 1.3.3 + prom-client: + specifier: ^14.0.1 + version: 14.2.0 + response-time: + specifier: ^2.3.2 + version: 2.3.2 + sass: + specifier: ^1.54.4 + version: 1.77.6 + serve-static: + specifier: ^1.15.0 + version: 1.15.0 + socket.io: + specifier: ^4.5.1 + version: 4.7.5 + socket.io-client: + specifier: ^4.5.1 + version: 4.7.5 + toastify-js: + specifier: ^1.9.1 + version: 1.12.0 + url-value-parser: + specifier: ^2.1.0 + version: 2.2.0 + winston: + specifier: ^3.3.3 + version: 3.13.0 + yargs: + specifier: ^17.7.2 + version: 17.7.2 + devDependencies: + '@snowpack/plugin-run-script': + specifier: ^2.3.0 + version: 2.3.0 + '@types/chai': + specifier: ^4.3.1 + version: 4.3.16 + '@types/compression': + specifier: ^1.7.0 + version: 1.7.5 + '@types/etag': + specifier: ^1.8.0 + version: 1.8.3 + '@types/express': + specifier: ^4.17.8 + version: 4.17.21 + '@types/fresh': + specifier: ^0.5.0 + version: 0.5.2 + '@types/fs-extra': + specifier: ^9.0.1 + version: 9.0.13 + '@types/gc-stats': + specifier: ^1 + version: 1.4.3 + '@types/helmet': + specifier: ^0.0.48 + version: 0.0.48 + '@types/jsdom': + specifier: ^12.2.4 + version: 12.2.4 + '@types/lodash': + specifier: ^4.14.161 + version: 4.17.6 + '@types/mocha': + specifier: ^9.1.1 + version: 9.1.1 + '@types/morgan': + specifier: ^1.7.37 + version: 1.9.9 + '@types/node': + specifier: ^20.2.6 + version: 20.14.9 + '@types/parseurl': + specifier: ^1.3.1 + version: 1.3.3 + '@types/response-time': + specifier: ^2 + version: 2.3.8 + '@types/serve-static': + specifier: ^1.15.3 + version: 1.15.7 + '@types/sinon': + specifier: ^10.0.13 + version: 10.0.20 + '@types/toastify-js': + specifier: ^1.9.2 + version: 1.12.3 + '@types/yargs': + specifier: ^17.0.24 + version: 17.0.32 + '@typescript-eslint/eslint-plugin': + specifier: ^5.59.9 + version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/parser': + specifier: ^5.59.9 + version: 5.62.0(eslint@8.57.0)(typescript@5.5.3) + all-contributors-cli: + specifier: ^6.17.2 + version: 6.26.1(encoding@0.1.13) + chai: + specifier: ^4.3.6 + version: 4.4.1 + concurrently: + specifier: ^8.2.2 + version: 8.2.2 + esbuild: + specifier: ^0.21.5 + version: 0.21.5 + esbuild-plugin-copy: + specifier: ^2.1.1 + version: 2.1.1(esbuild@0.21.5) + esbuild-sass-plugin: + specifier: ^3.3.1 + version: 3.3.1(esbuild@0.21.5)(sass-embedded@1.77.5) + eslint: + specifier: ^8.36.0 + version: 8.57.0 + eslint-config-airbnb-base: + specifier: latest + version: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-config-prettier: + specifier: ^8.6.0 + version: 8.10.0(eslint@8.57.0) + eslint-import-resolver-typescript: + specifier: ^3.4.0 + version: 3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: + specifier: ^2.27.5 + version: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-mocha: + specifier: ^10.1.0 + version: 10.4.3(eslint@8.57.0) + eslint-plugin-prettier: + specifier: ^4.2.1 + version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8) + git-authors-cli: + specifier: ^1.0.42 + version: 1.0.49 + husky: + specifier: ^9.0.11 + version: 9.0.11 + jsdom: + specifier: ^16.5.0 + version: 16.7.0 + lint-staged: + specifier: ^13.2.2 + version: 13.3.0 + mocha: + specifier: ^10.0.0 + version: 10.5.2 + nodemon: + specifier: ^3.1.4 + version: 3.1.4 + prettier: + specifier: ^2.5.1 + version: 2.8.8 + sinon: + specifier: ^14.0.0 + version: 14.0.2 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.14.9)(typescript@5.5.3) + typescript: + specifier: ^5.1.3 + version: 5.5.3 + vite: + specifier: ^6.2.6 + version: 6.4.1(@types/node@20.14.9)(sass-embedded@1.77.5)(sass@1.77.6) + +packages: + + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.24.7': + resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} + engines: {node: '>=6.9.0'} + + '@bufbuild/protobuf@1.10.0': + resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==} + + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@dabh/diagnostics@2.0.3': + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.11.0': + resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.0': + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@fortawesome/fontawesome-common-types@6.5.2': + resolution: {integrity: sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==} + engines: {node: '>=6'} + + '@fortawesome/fontawesome-svg-core@6.5.2': + resolution: {integrity: sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==} + engines: {node: '>=6'} + + '@fortawesome/free-solid-svg-icons@6.5.2': + resolution: {integrity: sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==} + engines: {node: '>=6'} + + '@gar/promisify@1.1.3': + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + + '@humanwhocodes/config-array@0.11.14': + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@npmcli/fs@2.1.2': + resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + '@npmcli/move-file@2.0.1': + resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This functionality has been moved to @npmcli/fs + + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} + cpu: [x64] + os: [win32] + + '@sinonjs/commons@1.8.6': + resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==} + + '@sinonjs/commons@2.0.0': + resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@11.2.2': + resolution: {integrity: sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==} + + '@sinonjs/fake-timers@9.1.2': + resolution: {integrity: sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==} + + '@sinonjs/samsam@7.0.1': + resolution: {integrity: sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==} + + '@sinonjs/text-encoding@0.7.2': + resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==} + + '@snowpack/plugin-run-script@2.3.0': + resolution: {integrity: sha512-rIbD67uzTPkzemMFbw5seUm/hZFrToTSqN/SGGq5XaU45ftX2sCRzp0lqSH+lZYhC5QTSm5RxXW1HcuPOlbPXQ==} + + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + + '@tootallnate/once@1.1.2': + resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} + engines: {node: '>= 6'} + + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/body-parser@1.19.5': + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + + '@types/chai@4.3.16': + resolution: {integrity: sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==} + + '@types/compression@1.7.5': + resolution: {integrity: sha512-AAQvK5pxMpaT+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDg==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cookie@0.4.1': + resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} + + '@types/cors@2.8.17': + resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/etag@1.8.3': + resolution: {integrity: sha512-QYHv9Yeh1ZYSMPQOoxY4XC4F1r+xRUiAriB303F4G6uBsT3KKX60DjiogvVv+2VISVDuJhcIzMdbjT+Bm938QQ==} + + '@types/express-serve-static-core@4.19.5': + resolution: {integrity: sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==} + + '@types/express@4.17.21': + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + + '@types/fresh@0.5.2': + resolution: {integrity: sha512-3WJILVS3mH25chJYG8WLzgohpXkqSE5S1dKFSWi+0O6hLjzZKL+AUvjFsk3YqwnUcRL6lQ2BR1IKVwedj5AiEw==} + + '@types/fs-extra@9.0.13': + resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} + + '@types/gc-stats@1.4.3': + resolution: {integrity: sha512-Aek/WiRt7EHDKFNI3hDjYVvPNEeSjWGxN/8jxyaFoAawAq/bd8geI04XPMLQlLBO7LneLoWAAAvFv7hedgdi3w==} + + '@types/helmet@0.0.48': + resolution: {integrity: sha512-C7MpnvSDrunS1q2Oy1VWCY7CDWHozqSnM8P4tFeRTuzwqni+PYOjEredwcqWG+kLpYcgLsgcY3orHB54gbx2Jw==} + + '@types/http-errors@2.0.4': + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + + '@types/jsdom@12.2.4': + resolution: {integrity: sha512-q+De3S/Ri6U9uPx89YA1XuC+QIBgndIfvBaaJG0pRT8Oqa75k4Mr7G9CRZjIvlbLGIukO/31DFGFJYlQBmXf/A==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/lodash@4.17.6': + resolution: {integrity: sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/mocha@9.1.1': + resolution: {integrity: sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==} + + '@types/morgan@1.9.9': + resolution: {integrity: sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ==} + + '@types/node@20.14.9': + resolution: {integrity: sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==} + + '@types/parseurl@1.3.3': + resolution: {integrity: sha512-eamlh+uXpNIG2yVdl6UdBTR3B6jtmJl/JWTNTQAN1K3VH1s5F6UPOhgLwCnU9FJ7R/PnUsRN9zFSpMcxwa2Ndg==} + + '@types/qs@6.9.15': + resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/response-time@2.3.8': + resolution: {integrity: sha512-7qGaNYvdxc0zRab8oHpYx7AW17qj+G0xuag1eCrw3M2VWPJQ/HyKaaghWygiaOUl0y9x7QGQwppDpqLJ5V9pzw==} + + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + + '@types/send@0.17.4': + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + + '@types/serve-static@1.15.7': + resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} + + '@types/sinon@10.0.20': + resolution: {integrity: sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==} + + '@types/sinonjs__fake-timers@8.1.5': + resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} + + '@types/toastify-js@1.12.3': + resolution: {integrity: sha512-9RjLlbAHMSaae/KZNHGv19VG4gcLIm3YjvacCXBtfMfYn26h76YP5oxXI8k26q4iKXCB9LNfv18lsoS0JnFPTg==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.32': + resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + + '@typescript-eslint/eslint-plugin@5.62.0': + resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@5.62.0': + resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/type-utils@5.62.0': + resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/typescript-estree@5.62.0': + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@5.62.0': + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@ungap/structured-clone@1.2.0': + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + + '@xterm/addon-fit@0.10.0': + resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/addon-image@0.8.0': + resolution: {integrity: sha512-b/dqpFn3jUad2pUP5UpF4scPIh0WdxRQL/1qyiahGfUI85XZTCXo0py9G6AcOR2QYUw8eJ8EowGspT7BQcgw6A==} + peerDependencies: + '@xterm/xterm': ^5.2.0 + + '@xterm/addon-web-links@0.11.0': + resolution: {integrity: sha512-nIHQ38pQI+a5kXnRaTgwqSHnX7KE6+4SVoceompgHL26unAxdfP6IPqUTSYPQgSwM56hsElfoNrrW5V7BUED/Q==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/xterm@5.5.0': + resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} + + abab@2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead + + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-globals@6.0.0: + resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@7.2.0: + resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} + engines: {node: '>=0.4.0'} + + acorn-walk@8.3.3: + resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} + engines: {node: '>=0.4.0'} + + acorn@7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + + acorn@8.12.0: + resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + all-contributors-cli@6.26.1: + resolution: {integrity: sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw==} + engines: {node: '>=4'} + hasBin: true + + ansi-colors@4.1.1: + resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} + engines: {node: '>=6'} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-escapes@5.0.0: + resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==} + engines: {node: '>=12'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + + are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + args-js@0.10.12: + resolution: {integrity: sha512-tR5vYVPA1lIqK4xr8F9ysseHuqgpqHhYe7i1iCW0Vh3zGjrqL9Fti/y5McZkRga2pWOC9Z9rnLIKW1l6I6AVgw==} + engines: {node: '>0.8.0', npm: '>1.1.0'} + + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} + + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + + async@3.2.5: + resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64id@2.0.0: + resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} + engines: {node: ^4.5.0 || >= 5.9} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bintrees@1.0.2: + resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + + body-parser@1.20.2: + resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browser-process-hrtime@1.0.0: + resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} + + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + + buffer-builder@0.2.0: + resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==} + + bytes@3.0.0: + resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + engines: {node: '>= 0.8'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cacache@16.1.3: + resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + cb2promise@1.1.1: + resolution: {integrity: sha512-ShCxBARPFJlSO+Y4pxSpbXh6y+tW54Dmy4jKf0gB1C7qUslRqWqFi80+W9416zSoj6RsqMsnKUcpkt3bOrZmDg==} + engines: {node: '>= 4'} + + chai@4.4.1: + resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} + engines: {node: '>=4'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + cli-truncate@3.1.0: + resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + colorspace@1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@11.0.0: + resolution: {integrity: sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==} + engines: {node: '>=16'} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.7.4: + resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} + engines: {node: '>= 0.8.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concurrently@8.2.2: + resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} + engines: {node: ^14.13.0 || >=16.0.0} + hasBin: true + + confusing-browser-globals@1.0.11: + resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + cssom@0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + + cssom@0.4.4: + resolution: {integrity: sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==} + + cssstyle@2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} + + data-urls@2.0.0: + resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==} + engines: {node: '>=10'} + + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.5: + resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + + decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + diff@5.0.0: + resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + engines: {node: '>=0.3.1'} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + domexception@2.0.1: + resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==} + engines: {node: '>=8'} + deprecated: Use your platform's native DOMException instead + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + email-regex@4.0.0: + resolution: {integrity: sha512-OxR2NqoYS3ZikqOkju2krRTyxngwjJ5Wh4yalpTqbBnUOr+LLwwjY2x5Sksruw6TieyQDswE5Pc83Eh6RQj3GA==} + engines: {node: '>=8'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + + engine.io-client@6.5.4: + resolution: {integrity: sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==} + + engine.io-parser@5.2.2: + resolution: {integrity: sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==} + engines: {node: '>=10.0.0'} + + engine.io@6.5.5: + resolution: {integrity: sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==} + engines: {node: '>=10.2.0'} + + enhanced-resolve@5.17.0: + resolution: {integrity: sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==} + engines: {node: '>=10.13.0'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + + es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + + esbuild-plugin-copy@2.1.1: + resolution: {integrity: sha512-Bk66jpevTcV8KMFzZI1P7MZKZ+uDcrZm2G2egZ2jNIvVnivDpodZI+/KnpL3Jnap0PBdIHU7HwFGB8r+vV5CVw==} + peerDependencies: + esbuild: '>= 0.14.0' + + esbuild-sass-plugin@3.3.1: + resolution: {integrity: sha512-SnO1ls+d52n6j8gRRpjexXI8MsHEaumS0IdDHaYM29Y6gakzZYMls6i9ql9+AWMSQk/eryndmUpXEgT34QrX1A==} + peerDependencies: + esbuild: '>=0.20.1' + sass-embedded: ^1.71.1 + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + eslint-config-airbnb-base@15.0.0: + resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} + engines: {node: ^10.12.0 || >=12.0.0} + peerDependencies: + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.2 + + eslint-config-prettier@8.10.0: + resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@3.6.1: + resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + + eslint-module-utils@2.8.1: + resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.29.1: + resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-mocha@10.4.3: + resolution: {integrity: sha512-emc4TVjq5Ht0/upR+psftuz6IBG5q279p+1dSRDeHf+NS9aaerBi3lXKo1SEzwC29hFIW21gO89CEWSvRsi8IQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@4.2.1: + resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-utils@3.0.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + + eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + execa@7.2.0: + resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} + engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} + + exponential-backoff@3.1.1: + resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} + + express-winston@4.2.0: + resolution: {integrity: sha512-EMD74g63nVHi7pFleQw7KHCxiA1pjF5uCwbCfzGqmFxs9KvlDPIVS3cMGpULm6MshExMT9TjC3SqmRGB9kb7yw==} + engines: {node: '>= 6'} + peerDependencies: + winston: '>=3.x <4' + + express@4.19.2: + resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} + engines: {node: '>= 0.10.0'} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-type@12.4.2: + resolution: {integrity: sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==} + engines: {node: '>=8'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + + form-data@3.0.1: + resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + engines: {node: '>= 6'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + gc-stats@1.4.1: + resolution: {integrity: sha512-eAvDBpI6UjVIYwLxshPCJJIkPyfamIrJzBtW/103+ooJWkISS+chVnHNnsZ+ubaw2607rFeiRDNWHkNUA+ioqg==} + engines: {node: '>=16'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.7.5: + resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} + + git-authors-cli@1.0.49: + resolution: {integrity: sha512-aodJH0A8DPxS4s2AyBhX3Z00Oz20PSJnff6JOC7AnfAQ0F8XEOmbQQNVSQW3pAzwW6os/RlTiNc9pDFWAQsk/Q==} + engines: {node: '>= 8'} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + helmet@4.6.0: + resolution: {integrity: sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==} + engines: {node: '>=10.0.0'} + + html-encoding-sniffer@2.0.1: + resolution: {integrity: sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==} + engines: {node: '>=10'} + + http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@4.0.1: + resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} + engines: {node: '>= 6'} + + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + human-signals@4.3.1: + resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} + engines: {node: '>=14.18.0'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + husky@9.0.11: + resolution: {integrity: sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==} + engines: {node: '>=18'} + hasBin: true + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + + ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + + immutable@4.3.6: + resolution: {integrity: sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + + inflight@1.0.6: + resolution: {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. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inquirer@7.3.3: + resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} + engines: {node: '>=8.0.0'} + + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + + ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.14.0: + resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-promise@1.0.1: + resolution: {integrity: sha512-mjWH5XxnhMA8cFnDchr6qRP9S/kLntKuEfIYku+PaN1CnS8v+OG9O/BKpRCVRJvpIkgAZm0Pf5Is3iSSOILlcg==} + + is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + + is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + + jsdom@16.7.0: + resolution: {integrity: sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==} + engines: {node: '>=10'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-fixer@1.6.15: + resolution: {integrity: sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==} + engines: {node: '>=10'} + + json-future@2.2.21: + resolution: {integrity: sha512-YWNZ0zMo5vNn9l+HSEvdhLB4X9UtS9/X1r5poy8Vx61VGiD86sXTT59n5HH+CzsU7mPIVAVcMKYTL8nfU5pNLw==} + engines: {node: '>= 4'} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + just-extend@6.2.0: + resolution: {integrity: sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lint-staged@13.3.0: + resolution: {integrity: sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==} + engines: {node: ^16.14.0 || >=18.0.0} + hasBin: true + + listr2@6.6.1: + resolution: {integrity: sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==} + engines: {node: '>=16.0.0'} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true + + load-json-file@6.2.0: + resolution: {integrity: sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==} + engines: {node: '>=8'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.get@4.4.2: + resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + log-update@5.0.1: + resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + logform@2.6.0: + resolution: {integrity: sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==} + engines: {node: '>= 12.0.0'} + + loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + make-fetch-happen@10.2.1: + resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-fn@2.0.0: + resolution: {integrity: sha512-jbex9Yd/3lmICXwYT6gA/j2mNQGU48wCh/VzRd+/Y/PjYQtlg1gLMdZqvu9s/xH7qKvngxRObl56XZR609IMbA==} + engines: {node: '>=6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.0.1: + resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + + minipass-fetch@2.1.2: + resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mocha@10.5.2: + resolution: {integrity: sha512-9btlN3JKCefPf+vKd/kcKz2SXxi12z6JswkGfaAF0saQvnsqLJk504ZmbxhSoENge08E9dsymozKgFMTl5PQsA==} + engines: {node: '>= 14.0.0'} + hasBin: true + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + + nan@2.20.0: + resolution: {integrity: sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare-lite@1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + nise@5.1.9: + resolution: {integrity: sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-gyp-build@4.8.1: + resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} + hasBin: true + + node-gyp@9.4.1: + resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==} + engines: {node: ^12.13 || ^14.13 || >=16} + hasBin: true + + node-pty@0.10.1: + resolution: {integrity: sha512-JTdtUS0Im/yRsWJSx7yiW9rtpfmxqxolrtnyKwPLI+6XqTAPW/O2MjS8FYL4I5TsMbH2lVgDb2VMjp+9LoQGNg==} + + nodeify@1.0.1: + resolution: {integrity: sha512-n7C2NyEze8GCo/z73KdbjRsBiLbv6eBn1FxwYKQ23IqGo7pQY3mhQan61Sv7eEDJCiyUjTVrVkXTzJCo1dW7Aw==} + + nodemon@3.1.4: + resolution: {integrity: sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==} + engines: {node: '>=10'} + hasBin: true + + nopt@6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + nwsapi@2.2.10: + resolution: {integrity: sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + + object.entries@1.1.8: + resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse5@4.0.0: + resolution: {integrity: sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + + path-to-regexp@6.2.2: + resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + + pegjs@0.10.0: + resolution: {integrity: sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==} + engines: {node: '>=0.10'} + hasBin: true + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + pify@5.0.0: + resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} + engines: {node: '>=10'} + + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + prom-client@14.2.0: + resolution: {integrity: sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA==} + engines: {node: '>=10'} + + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + + promise@1.3.0: + resolution: {integrity: sha512-R9WrbTF3EPkVtWjp7B7umQGVndpsi+rsDAfrR4xAALQpFLa/+2OriecLhawxzvii2gd9+DZFwROWDuUUaqS5yA==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + + pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + rambda@7.5.0: + resolution: {integrity: sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + response-time@2.3.2: + resolution: {integrity: sha512-MUIDaDQf+CVqflfTdQ5yam+aYCkXj1PY8fjlPDQ6ppxJlmgZb864pHtA750mayywNg8tx4rS7qH9JXd/OF+3gw==} + engines: {node: '>= 0.8.0'} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@6.6.7: + resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} + engines: {npm: '>=2.0.0'} + + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-identifier@0.4.2: + resolution: {integrity: sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==} + + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} + + safe-stable-stringify@2.4.3: + resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sass-embedded-android-arm64@1.77.5: + resolution: {integrity: sha512-t4yIhK5OUpg1coZxFpDo3BhI2YVj21JxEd5SVI6FfcWD2ESroQWsC4cbq3ejw5aun8R1Kx6xH1EKxO8bSMvn1g==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [android] + hasBin: true + + sass-embedded-android-arm@1.77.5: + resolution: {integrity: sha512-/DfNYoykqwMFduecqa8n0NH+cS6oLdCPFjwhe92efsOOt5WDYEOlolnhoOENZxqdzvSV+8axL+mHQ1Ypl4MLtg==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [android] + hasBin: true + + sass-embedded-android-ia32@1.77.5: + resolution: {integrity: sha512-92dWhEbR0Z2kpjbpfOx4LM9wlNBSnDsRtwpkMUK8udQIE7uF3E4/Fsf/88IJk0MrRkk4iwrsxxiCb1bz2tWnHQ==} + engines: {node: '>=14.0.0'} + cpu: [ia32] + os: [android] + hasBin: true + + sass-embedded-android-x64@1.77.5: + resolution: {integrity: sha512-lFnXz9lRnjRLJ8Y28ONJViID3rDq4p6LJ/9ByPk2ZnSpx5ouUjsu4AfrXKJ0jgHWBaDvSKSxq2fPpt5aMQAEZA==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [android] + hasBin: true + + sass-embedded-darwin-arm64@1.77.5: + resolution: {integrity: sha512-J3yP6w+xqPrGQE0+sO4Gam6kBDJL5ivgkFNxR0fVlvKeN5qVFYhymp/xGRRMxBrKjohEQtBGP431EzrtvUMFow==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [darwin] + hasBin: true + + sass-embedded-darwin-x64@1.77.5: + resolution: {integrity: sha512-A9fh5tg4s0FidMTG31Vs8TzYZ3Mam/I/tfqvN0g512OhBajp/p2DJvBY+0Br2r+TNH1yGUXf2ZfULuTBFj5u8w==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [darwin] + hasBin: true + + sass-embedded-linux-arm64@1.77.5: + resolution: {integrity: sha512-LoN804X7QsyvT/h8UGcgBMfV1SdT4JRRNV+slBICxoXPKBLXbZm9KyLRCBQcMLLdlXSZdOfZilxUN1Bd2az6OA==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + hasBin: true + + sass-embedded-linux-arm@1.77.5: + resolution: {integrity: sha512-O7gbOWJloxITBZNkpwChFltxofsnDUf+3pz7+q2ETQKvZQ3kUfFENAF37slo0bsHJ7IEpwJK3ZJlnhZvIgfhgw==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + hasBin: true + + sass-embedded-linux-ia32@1.77.5: + resolution: {integrity: sha512-KHNJymlEmjyJbhGfB34zowohjgMvv/qKVsDX5hPlar+qMh+cxJwfgPln1Zl9bfe9qLObmEV2zFA1rpVBWy4xGQ==} + engines: {node: '>=14.0.0'} + cpu: [ia32] + os: [linux] + hasBin: true + + sass-embedded-linux-musl-arm64@1.77.5: + resolution: {integrity: sha512-ZWl8K8rCL4/phm3IPWDADwjnYAiohoaKg7BKjGo+36zv8P0ocoA0A3j4xx7/kjUJWagOmmoTyYxoOu+lo1NaKw==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + + sass-embedded-linux-musl-arm@1.77.5: + resolution: {integrity: sha512-TLhJzd1TJ0oX1oULobkWLMDLeErD27WbhdZqxtFvIqzyO+1TZPMwojhRX4YNWmHdmmYhIuXTR9foWxwL3Xjgsg==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + + sass-embedded-linux-musl-ia32@1.77.5: + resolution: {integrity: sha512-83zNSgsIIc+tYQFKepFIlvAvAHnbWSpZ824MjqXJLeCbfzcMO8SZ/q6OA0Zd2SIrf79lCWI4OfPHqp1PI6M7HQ==} + engines: {node: '>=14.0.0'} + cpu: [ia32] + os: [linux] + + sass-embedded-linux-musl-x64@1.77.5: + resolution: {integrity: sha512-/SW9ggXZJilbRbKvRHAxEuQM6Yr9piEpvK7/aDevFL2XFvBW9x+dTzpH5jPVEmM0qWdJisS1r5mEv8AXUUdQZg==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + + sass-embedded-linux-x64@1.77.5: + resolution: {integrity: sha512-3EmYeY+K8nMwIy1El9C+mPuONMQyXSCD6Yyztn3G7moPdZTqXrTL7kTJIl+SRq1tCcnOMMGXnBRE7Kpou1wd+w==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + hasBin: true + + sass-embedded-win32-arm64@1.77.5: + resolution: {integrity: sha512-dwVFOqkyfCRQgQB8CByH+MG93fp7IsfFaPDDCQVzVFAT00+HXk/dWFPMnv65XDDndGwsUE1KlZnjg8iOBDlRdw==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [win32] + hasBin: true + + sass-embedded-win32-ia32@1.77.5: + resolution: {integrity: sha512-1ij/K5d2sHPJkytWiPJLoUOVHJOB6cSWXq7jmedeuGooWnBmqnWycmGkhBAEK/t6t1XgzMPsiJMGiHKh7fnBuA==} + engines: {node: '>=14.0.0'} + cpu: [ia32] + os: [win32] + hasBin: true + + sass-embedded-win32-x64@1.77.5: + resolution: {integrity: sha512-Pn6j0jDGeEAhuuVY0CaZaBa7yNkqimEsbUDYYuQ9xh+XdGvZ86SZf6HXHUVIyQUjHORLwQ5f0XoKYYzKfC0y9w==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [win32] + hasBin: true + + sass-embedded@1.77.5: + resolution: {integrity: sha512-JQI8aprHDRSNK5exXsbusswTENQPJxW1QWUcLdwuyESoJClT1zo8e+4cmaV5OAU4abcRC6Av4/RmLocPdjcR3A==} + engines: {node: '>=16.0.0'} + + sass@1.77.6: + resolution: {integrity: sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==} + engines: {node: '>=14.0.0'} + hasBin: true + + saxes@5.0.1: + resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} + engines: {node: '>=10'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.2: + resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + engines: {node: '>=10'} + hasBin: true + + send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + + serialize-javascript@6.0.0: + resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + + serve-static@1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.1: + resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + + sinon@14.0.2: + resolution: {integrity: sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==} + deprecated: 16.1.1 + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + sliced@1.0.1: + resolution: {integrity: sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socket.io-adapter@2.5.5: + resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} + + socket.io-client@4.7.5: + resolution: {integrity: sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==} + engines: {node: '>=10.0.0'} + + socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + + socket.io@4.7.5: + resolution: {integrity: sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==} + engines: {node: '>=10.2.0'} + + socks-proxy-agent@7.0.0: + resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} + engines: {node: '>= 10'} + + socks@2.8.3: + resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + sort-keys@4.2.0: + resolution: {integrity: sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg==} + engines: {node: '>=8'} + + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spawn-command@0.0.2: + resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + ssri@9.0.1: + resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + tdigest@0.1.2: + resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} + + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyspawn@1.2.14: + resolution: {integrity: sha512-/PYwakpVcbTLgUmElZGTsSICm7g1YuBtgX8y3sigivlgpFuVvxsIx82xruW3iUOJF+u2gj/6nbtGcC2pAVsvyg==} + engines: {node: '>= 18'} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toastify-js@1.12.0: + resolution: {integrity: sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + touch@3.1.1: + resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} + hasBin: true + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tr46@2.1.0: + resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==} + engines: {node: '>=8'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + + type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} + + typedarray-to-buffer@3.1.5: + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + + typescript@5.5.3: + resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + + undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + unique-filename@2.0.1: + resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + unique-slug@3.0.0: + resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + url-value-parser@2.2.0: + resolution: {integrity: sha512-yIQdxJpgkPamPPAPuGdS7Q548rLhny42tg8d4vyTNzFqvOnwqrgHXvgehT09U7fwrzxi3RxCiXjoNUNnNOlQ8A==} + engines: {node: '>=6.0.0'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + varint@6.0.0: + resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + w3c-hr-time@1.0.2: + resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} + deprecated: Use your platform's native performance.now() and performance.timeOrigin. + + w3c-xmlserializer@2.0.0: + resolution: {integrity: sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==} + engines: {node: '>=10'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@5.0.0: + resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==} + engines: {node: '>=8'} + + webidl-conversions@6.1.0: + resolution: {integrity: sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==} + engines: {node: '>=10.4'} + + whatwg-encoding@1.0.5: + resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==} + + whatwg-mimetype@2.3.0: + resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + whatwg-url@8.7.0: + resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==} + engines: {node: '>=10'} + + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + winston-transport@4.7.0: + resolution: {integrity: sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==} + engines: {node: '>= 12.0.0'} + + winston@3.13.0: + resolution: {integrity: sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==} + engines: {node: '>= 12.0.0'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + workerpool@6.2.1: + resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@3.0.3: + resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + + write-json-file@4.3.0: + resolution: {integrity: sha512-PxiShnxf0IlnQuMYOPPhPkhExoCQuTUNPOa/2JWCYTmBquU9njyyDuwRKN26IZBlp4yn1nt+Agh2HOOBl+55HQ==} + engines: {node: '>=8.3'} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@3.0.0: + resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + xmlhttprequest-ssl@2.0.0: + resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} + engines: {node: '>=0.4.0'} + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml@2.3.1: + resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} + engines: {node: '>= 14'} + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs-parser@20.2.4: + resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + + '@babel/runtime@7.24.7': + dependencies: + regenerator-runtime: 0.14.1 + + '@bufbuild/protobuf@1.10.0': {} + + '@colors/colors@1.6.0': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@dabh/diagnostics@2.0.3': + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + dependencies: + eslint: 8.57.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.11.0': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.3.5(supports-color@5.5.0) + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.0': {} + + '@fortawesome/fontawesome-common-types@6.5.2': {} + + '@fortawesome/fontawesome-svg-core@6.5.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.5.2 + + '@fortawesome/free-solid-svg-icons@6.5.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.5.2 + + '@gar/promisify@1.1.3': {} + + '@humanwhocodes/config-array@0.11.14': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.3.5(supports-color@5.5.0) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.4.15': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@npmcli/fs@2.1.2': + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.6.2 + + '@npmcli/move-file@2.0.1': + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + + '@rollup/rollup-android-arm-eabi@4.59.0': + optional: true + + '@rollup/rollup-android-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-x64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.59.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.59.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.59.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.59.0': + optional: true + + '@sinonjs/commons@1.8.6': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/commons@2.0.0': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@11.2.2': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@sinonjs/fake-timers@9.1.2': + dependencies: + '@sinonjs/commons': 1.8.6 + + '@sinonjs/samsam@7.0.1': + dependencies: + '@sinonjs/commons': 2.0.0 + lodash.get: 4.4.2 + type-detect: 4.0.8 + + '@sinonjs/text-encoding@0.7.2': {} + + '@snowpack/plugin-run-script@2.3.0': + dependencies: + execa: 5.1.1 + npm-run-path: 4.0.1 + + '@socket.io/component-emitter@3.1.2': {} + + '@tootallnate/once@1.1.2': {} + + '@tootallnate/once@2.0.0': {} + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/body-parser@1.19.5': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.14.9 + + '@types/chai@4.3.16': {} + + '@types/compression@1.7.5': + dependencies: + '@types/express': 4.17.21 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.14.9 + + '@types/cookie@0.4.1': {} + + '@types/cors@2.8.17': + dependencies: + '@types/node': 20.14.9 + + '@types/estree@1.0.8': {} + + '@types/etag@1.8.3': + dependencies: + '@types/node': 20.14.9 + + '@types/express-serve-static-core@4.19.5': + dependencies: + '@types/node': 20.14.9 + '@types/qs': 6.9.15 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 + + '@types/express@4.17.21': + dependencies: + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 4.19.5 + '@types/qs': 6.9.15 + '@types/serve-static': 1.15.7 + + '@types/fresh@0.5.2': {} + + '@types/fs-extra@9.0.13': + dependencies: + '@types/node': 20.14.9 + + '@types/gc-stats@1.4.3': + dependencies: + '@types/node': 20.14.9 + + '@types/helmet@0.0.48': + dependencies: + '@types/express': 4.17.21 + + '@types/http-errors@2.0.4': {} + + '@types/jsdom@12.2.4': + dependencies: + '@types/node': 20.14.9 + '@types/tough-cookie': 4.0.5 + parse5: 4.0.0 + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/lodash@4.17.6': {} + + '@types/mime@1.3.5': {} + + '@types/mocha@9.1.1': {} + + '@types/morgan@1.9.9': + dependencies: + '@types/node': 20.14.9 + + '@types/node@20.14.9': + dependencies: + undici-types: 5.26.5 + + '@types/parseurl@1.3.3': + dependencies: + '@types/node': 20.14.9 + + '@types/qs@6.9.15': {} + + '@types/range-parser@1.2.7': {} + + '@types/response-time@2.3.8': + dependencies: + '@types/express': 4.17.21 + '@types/node': 20.14.9 + + '@types/semver@7.5.8': {} + + '@types/send@0.17.4': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.14.9 + + '@types/serve-static@1.15.7': + dependencies: + '@types/http-errors': 2.0.4 + '@types/node': 20.14.9 + '@types/send': 0.17.4 + + '@types/sinon@10.0.20': + dependencies: + '@types/sinonjs__fake-timers': 8.1.5 + + '@types/sinonjs__fake-timers@8.1.5': {} + + '@types/toastify-js@1.12.3': {} + + '@types/tough-cookie@4.0.5': {} + + '@types/triple-beam@1.3.5': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.32': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@eslint-community/regexpp': 4.11.0 + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.5.3) + debug: 4.3.5(supports-color@5.5.0) + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare-lite: 1.4.0 + semver: 7.6.2 + tsutils: 3.21.0(typescript@5.5.3) + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.3) + debug: 4.3.5(supports-color@5.5.0) + eslint: 8.57.0 + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + + '@typescript-eslint/type-utils@5.62.0(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.5.3) + debug: 4.3.5(supports-color@5.5.0) + eslint: 8.57.0 + tsutils: 3.21.0(typescript@5.5.3) + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@5.62.0': {} + + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.5.3)': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.3.5(supports-color@5.5.0) + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.6.2 + tsutils: 3.21.0(typescript@5.5.3) + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.3) + eslint: 8.57.0 + eslint-scope: 5.1.1 + semver: 7.6.2 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.2.0': {} + + '@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/addon-image@0.8.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/addon-web-links@0.11.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/xterm@5.5.0': {} + + abab@2.0.6: {} + + abbrev@1.1.1: {} + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-globals@6.0.0: + dependencies: + acorn: 7.4.1 + acorn-walk: 7.2.0 + + acorn-jsx@5.3.2(acorn@8.12.0): + dependencies: + acorn: 8.12.0 + + acorn-walk@7.2.0: {} + + acorn-walk@8.3.3: + dependencies: + acorn: 8.12.0 + + acorn@7.4.1: {} + + acorn@8.12.0: {} + + agent-base@6.0.2: + dependencies: + debug: 4.3.5(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + agentkeepalive@4.5.0: + dependencies: + humanize-ms: 1.2.1 + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + all-contributors-cli@6.26.1(encoding@0.1.13): + dependencies: + '@babel/runtime': 7.24.7 + async: 3.2.5 + chalk: 4.1.2 + didyoumean: 1.2.2 + inquirer: 7.3.3 + json-fixer: 1.6.15 + lodash: 4.17.21 + node-fetch: 2.7.0(encoding@0.1.13) + pify: 5.0.0 + yargs: 15.4.1 + optionalDependencies: + prettier: 2.8.8 + transitivePeerDependencies: + - encoding + + ansi-colors@4.1.1: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-escapes@5.0.0: + dependencies: + type-fest: 1.4.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + aproba@2.0.0: {} + + are-we-there-yet@3.0.1: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + + arg@4.1.3: {} + + argparse@2.0.1: {} + + args-js@0.10.12: {} + + array-buffer-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + + array-flatten@1.1.1: {} + + array-includes@3.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + is-string: 1.0.7 + + array-union@2.1.0: {} + + array.prototype.findlastindex@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + + array.prototype.flat@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.flatmap@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + arraybuffer.prototype.slice@1.0.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 + + assertion-error@1.1.0: {} + + async@3.2.5: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 + + balanced-match@1.0.2: {} + + base64id@2.0.0: {} + + binary-extensions@2.3.0: {} + + bintrees@1.0.2: {} + + body-parser@1.20.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browser-process-hrtime@1.0.0: {} + + browser-stdout@1.3.1: {} + + buffer-builder@0.2.0: {} + + bytes@3.0.0: {} + + bytes@3.1.2: {} + + cacache@16.1.3: + dependencies: + '@npmcli/fs': 2.1.2 + '@npmcli/move-file': 2.0.1 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 8.1.0 + infer-owner: 1.0.4 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1 + rimraf: 3.0.2 + ssri: 9.0.1 + tar: 6.2.1 + unique-filename: 2.0.1 + transitivePeerDependencies: + - bluebird + + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + cb2promise@1.1.1: + dependencies: + mimic-fn: 2.0.0 + sliced: 1.0.1 + + chai@4.4.1: + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.4 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.0.8 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.3.0: {} + + chardet@0.7.0: {} + + check-error@1.0.3: + dependencies: + get-func-name: 2.0.2 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chownr@2.0.0: {} + + clean-stack@2.2.0: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-cursor@4.0.0: + dependencies: + restore-cursor: 4.0.0 + + cli-truncate@3.1.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 5.1.2 + + cli-width@3.0.0: {} + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color-support@1.1.3: {} + + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + + colorette@2.0.20: {} + + colorspace@1.1.4: + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@11.0.0: {} + + compressible@2.0.18: + dependencies: + mime-db: 1.52.0 + + compression@1.7.4: + dependencies: + accepts: 1.3.8 + bytes: 3.0.0 + compressible: 2.0.18 + debug: 2.6.9 + on-headers: 1.0.2 + safe-buffer: 5.1.2 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + concat-map@0.0.1: {} + + concurrently@8.2.2: + dependencies: + chalk: 4.1.2 + date-fns: 2.30.0 + lodash: 4.17.21 + rxjs: 7.8.1 + shell-quote: 1.8.1 + spawn-command: 0.0.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + + confusing-browser-globals@1.0.11: {} + + console-control-strings@1.1.0: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.6: {} + + cookie@0.4.2: {} + + cookie@0.6.0: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + create-require@1.1.1: {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssom@0.3.8: {} + + cssom@0.4.4: {} + + cssstyle@2.3.0: + dependencies: + cssom: 0.3.8 + + data-urls@2.0.0: + dependencies: + abab: 2.0.6 + whatwg-mimetype: 2.3.0 + whatwg-url: 8.7.0 + + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.24.7 + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.3.4(supports-color@8.1.1): + dependencies: + ms: 2.1.2 + optionalDependencies: + supports-color: 8.1.1 + + debug@4.3.5(supports-color@5.5.0): + dependencies: + ms: 2.1.2 + optionalDependencies: + supports-color: 5.5.0 + + decamelize@1.2.0: {} + + decamelize@4.0.0: {} + + decimal.js@10.4.3: {} + + deep-eql@4.1.4: + dependencies: + type-detect: 4.0.8 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delayed-stream@1.0.0: {} + + delegates@1.0.0: {} + + depd@1.1.2: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + detect-indent@6.1.0: {} + + didyoumean@1.2.2: {} + + diff@4.0.2: {} + + diff@5.0.0: {} + + diff@5.2.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + domexception@2.0.1: + dependencies: + webidl-conversions: 5.0.0 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + email-regex@4.0.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enabled@2.0.0: {} + + encodeurl@1.0.2: {} + + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + + engine.io-client@6.5.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.5(supports-color@5.5.0) + engine.io-parser: 5.2.2 + ws: 8.17.1 + xmlhttprequest-ssl: 2.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + engine.io-parser@5.2.2: {} + + engine.io@6.5.5: + dependencies: + '@types/cookie': 0.4.1 + '@types/cors': 2.8.17 + '@types/node': 20.14.9 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.4.2 + cors: 2.8.5 + debug: 4.3.5(supports-color@5.5.0) + engine.io-parser: 5.2.2 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + enhanced-resolve@5.17.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + + env-paths@2.2.1: {} + + err-code@2.0.3: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.23.3: + dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 + globalthis: 1.0.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 + is-callable: 1.2.7 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + is-string: 1.0.7 + is-typed-array: 1.1.13 + is-weakref: 1.0.2 + object-inspect: 1.13.2 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.15 + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-errors@1.3.0: {} + + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.0.3: + dependencies: + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.0.2: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.2.1: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + esbuild-plugin-copy@2.1.1(esbuild@0.21.5): + dependencies: + chalk: 4.1.2 + chokidar: 3.6.0 + esbuild: 0.21.5 + fs-extra: 10.1.0 + globby: 11.1.0 + + esbuild-sass-plugin@3.3.1(esbuild@0.21.5)(sass-embedded@1.77.5): + dependencies: + esbuild: 0.21.5 + resolve: 1.22.8 + safe-identifier: 0.4.2 + sass: 1.77.6 + sass-embedded: 1.77.5 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.1.2: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0): + dependencies: + confusing-browser-globals: 1.0.11 + eslint: 8.57.0 + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + object.assign: 4.1.5 + object.entries: 1.1.8 + semver: 6.3.1 + + eslint-config-prettier@8.10.0(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.14.0 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0): + dependencies: + debug: 4.3.5(supports-color@5.5.0) + enhanced-resolve: 5.17.0 + eslint: 8.57.0 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + fast-glob: 3.3.2 + get-tsconfig: 4.7.5 + is-core-module: 2.14.0 + is-glob: 4.0.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.5.3) + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + dependencies: + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + hasown: 2.0.2 + is-core-module: 2.14.0 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.5.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-mocha@10.4.3(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + eslint-utils: 3.0.0(eslint@8.57.0) + globals: 13.24.0 + rambda: 7.5.0 + + eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8): + dependencies: + eslint: 8.57.0 + prettier: 2.8.8 + prettier-linter-helpers: 1.0.0 + optionalDependencies: + eslint-config-prettier: 8.10.0(eslint@8.57.0) + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-utils@3.0.0(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + eslint-visitor-keys: 2.1.0 + + eslint-visitor-keys@2.1.0: {} + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.0: + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/regexpp': 4.11.0 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.0 + '@humanwhocodes/config-array': 0.11.14 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.5(supports-color@5.5.0) + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.1 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.12.0 + acorn-jsx: 5.3.2(acorn@8.12.0) + eslint-visitor-keys: 3.4.3 + + esprima@4.0.1: {} + + esquery@1.5.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + eventemitter3@5.0.1: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@7.2.0: + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 4.3.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + + exponential-backoff@3.1.1: {} + + express-winston@4.2.0(winston@3.13.0): + dependencies: + chalk: 2.4.2 + lodash: 4.17.21 + winston: 3.13.0 + + express@4.19.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.2 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.6.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.7 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-safe-stringify@2.1.1: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fecha@4.2.3: {} + + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + file-type@12.4.2: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.2.0: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + rimraf: 3.0.2 + + flat@5.0.2: {} + + flatted@3.3.1: {} + + fn.name@1.1.0: {} + + for-each@0.3.3: + dependencies: + is-callable: 1.2.7 + + form-data@3.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + functions-have-names: 1.2.3 + + functions-have-names@1.2.3: {} + + gauge@4.0.4: + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + + gc-stats@1.4.1: + dependencies: + nan: 2.20.0 + node-gyp-build: 4.8.1 + + get-caller-file@2.0.5: {} + + get-func-name@2.0.2: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + + get-stream@6.0.1: {} + + get-symbol-description@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + + get-tsconfig@4.7.5: + dependencies: + resolve-pkg-maps: 1.0.0 + + git-authors-cli@1.0.49: + dependencies: + email-regex: 4.0.0 + json-future: 2.2.21 + mri: 1.2.0 + picocolors: 1.0.1 + tinyspawn: 1.2.14 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.0.1 + once: 1.4.0 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.0.1 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-bigints@1.0.2: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.0.3 + + has-unicode@2.0.1: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + helmet@4.6.0: {} + + html-encoding-sniffer@2.0.1: + dependencies: + whatwg-encoding: 1.0.5 + + http-cache-semantics@4.1.1: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-proxy-agent@4.0.1: + dependencies: + '@tootallnate/once': 1.1.2 + agent-base: 6.0.2 + debug: 4.3.5(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.3.5(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.3.5(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + human-signals@4.3.1: {} + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + husky@9.0.11: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + + ignore-by-default@1.0.1: {} + + ignore@5.3.1: {} + + immutable@4.3.6: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + infer-owner@1.0.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + inquirer@7.3.3: + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + run-async: 2.4.1 + rxjs: 6.6.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + + internal-slot@1.0.7: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 + + ip-address@9.0.5: + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + + ipaddr.js@1.9.1: {} + + is-array-buffer@3.0.4: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-arrayish@0.2.1: {} + + is-arrayish@0.3.2: {} + + is-bigint@1.0.4: + dependencies: + has-bigints: 1.0.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.1.2: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.14.0: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.1: + dependencies: + is-typed-array: 1.1.13 + + is-date-object@1.0.5: + dependencies: + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-lambda@1.0.1: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@2.1.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-promise@1.0.1: {} + + is-regex@1.1.4: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-shared-array-buffer@1.0.3: + dependencies: + call-bind: 1.0.7 + + is-stream@2.0.1: {} + + is-stream@3.0.0: {} + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.0.3 + + is-typed-array@1.1.13: + dependencies: + which-typed-array: 1.1.15 + + is-typedarray@1.0.0: {} + + is-unicode-supported@0.1.0: {} + + is-weakref@1.0.2: + dependencies: + call-bind: 1.0.7 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsbn@1.1.0: {} + + jsdom@16.7.0: + dependencies: + abab: 2.0.6 + acorn: 8.12.0 + acorn-globals: 6.0.0 + cssom: 0.4.4 + cssstyle: 2.3.0 + data-urls: 2.0.0 + decimal.js: 10.4.3 + domexception: 2.0.1 + escodegen: 2.1.0 + form-data: 3.0.1 + html-encoding-sniffer: 2.0.1 + http-proxy-agent: 4.0.1 + https-proxy-agent: 5.0.1 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.10 + parse5: 6.0.1 + saxes: 5.0.1 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-hr-time: 1.0.2 + w3c-xmlserializer: 2.0.0 + webidl-conversions: 6.1.0 + whatwg-encoding: 1.0.5 + whatwg-mimetype: 2.3.0 + whatwg-url: 8.7.0 + ws: 7.5.10 + xml-name-validator: 3.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + json-buffer@3.0.1: {} + + json-fixer@1.6.15: + dependencies: + '@babel/runtime': 7.24.7 + chalk: 4.1.2 + pegjs: 0.10.0 + + json-future@2.2.21: + dependencies: + args-js: 0.10.12 + async: 3.2.5 + cb2promise: 1.1.1 + fast-safe-stringify: 2.1.1 + load-json-file: 6.2.0 + nodeify: 1.0.1 + parse-json: 5.2.0 + sliced: 1.0.1 + write-json-file: 4.3.0 + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + just-extend@6.2.0: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kuler@2.0.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@2.1.0: {} + + lines-and-columns@1.2.4: {} + + lint-staged@13.3.0: + dependencies: + chalk: 5.3.0 + commander: 11.0.0 + debug: 4.3.4(supports-color@8.1.1) + execa: 7.2.0 + lilconfig: 2.1.0 + listr2: 6.6.1 + micromatch: 4.0.5 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.3.1 + transitivePeerDependencies: + - enquirer + - supports-color + + listr2@6.6.1: + dependencies: + cli-truncate: 3.1.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 5.0.1 + rfdc: 1.4.1 + wrap-ansi: 8.1.0 + + load-json-file@6.2.0: + dependencies: + graceful-fs: 4.2.11 + parse-json: 5.2.0 + strip-bom: 4.0.0 + type-fest: 0.6.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.get@4.4.2: {} + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + log-update@5.0.1: + dependencies: + ansi-escapes: 5.0.0 + cli-cursor: 4.0.0 + slice-ansi: 5.0.0 + strip-ansi: 7.1.0 + wrap-ansi: 8.1.0 + + logform@2.6.0: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.4.3 + triple-beam: 1.4.1 + + loupe@2.3.7: + dependencies: + get-func-name: 2.0.2 + + lru-cache@7.18.3: {} + + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + + make-error@1.3.6: {} + + make-fetch-happen@10.2.1: + dependencies: + agentkeepalive: 4.5.0 + cacache: 16.1.3 + http-cache-semantics: 4.1.1 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 2.1.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.3 + promise-retry: 2.0.1 + socks-proxy-agent: 7.0.0 + ssri: 9.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + + media-typer@0.3.0: {} + + merge-descriptors@1.0.1: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + methods@1.1.2: {} + + micromatch@4.0.5: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mimic-fn@2.0.0: {} + + mimic-fn@2.1.0: {} + + mimic-fn@4.0.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@5.0.1: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass-collect@1.0.2: + dependencies: + minipass: 3.3.6 + + minipass-fetch@2.1.2: + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp@1.0.4: {} + + mocha@10.5.2: + dependencies: + ansi-colors: 4.1.1 + browser-stdout: 1.3.1 + chokidar: 3.6.0 + debug: 4.3.4(supports-color@8.1.1) + diff: 5.0.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 8.1.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.0.1 + ms: 2.1.3 + serialize-javascript: 6.0.0 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.2.1 + yargs: 16.2.0 + yargs-parser: 20.2.4 + yargs-unparser: 2.0.0 + + mri@1.2.0: {} + + ms@2.0.0: {} + + ms@2.1.2: {} + + ms@2.1.3: {} + + mute-stream@0.0.8: {} + + nan@2.20.0: {} + + nanoid@3.3.11: {} + + natural-compare-lite@1.4.0: {} + + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + nise@5.1.9: + dependencies: + '@sinonjs/commons': 3.0.1 + '@sinonjs/fake-timers': 11.2.2 + '@sinonjs/text-encoding': 0.7.2 + just-extend: 6.2.0 + path-to-regexp: 6.2.2 + + node-fetch@2.7.0(encoding@0.1.13): + dependencies: + whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 + + node-gyp-build@4.8.1: {} + + node-gyp@9.4.1: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 10.2.1 + nopt: 6.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.6.2 + tar: 6.2.1 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + + node-pty@0.10.1: + dependencies: + nan: 2.20.0 + + nodeify@1.0.1: + dependencies: + is-promise: 1.0.1 + promise: 1.3.0 + + nodemon@3.1.4: + dependencies: + chokidar: 3.6.0 + debug: 4.3.5(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 7.6.2 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.1 + undefsafe: 2.0.5 + + nopt@6.0.0: + dependencies: + abbrev: 1.1.1 + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + npmlog@6.0.2: + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + + nwsapi@2.2.10: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.2: {} + + object-keys@1.1.1: {} + + object.assign@4.1.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + object.entries@1.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + + object.values@1.2.0: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.0.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + os-tmpdir@1.0.2: {} + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + p-try@2.2.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.24.7 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5@4.0.0: {} + + parse5@6.0.1: {} + + parseurl@1.3.3: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-to-regexp@0.1.7: {} + + path-to-regexp@6.2.2: {} + + path-type@4.0.0: {} + + pathval@1.1.1: {} + + pegjs@0.10.0: {} + + picocolors@1.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.6.0: {} + + pify@5.0.0: {} + + possible-typed-array-names@1.0.0: {} + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@2.8.8: {} + + prom-client@14.2.0: + dependencies: + tdigest: 0.1.2 + + promise-inflight@1.0.1: {} + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + + promise@1.3.0: + dependencies: + is-promise: 1.0.1 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + psl@1.9.0: {} + + pstree.remy@1.1.8: {} + + punycode@2.3.1: {} + + qs@6.11.0: + dependencies: + side-channel: 1.0.6 + + querystringify@2.2.0: {} + + queue-microtask@1.2.3: {} + + rambda@7.5.0: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + range-parser@1.2.1: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + regenerator-runtime@0.14.1: {} + + regexp.prototype.flags@1.5.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 + + require-directory@2.1.1: {} + + require-main-filename@2.0.0: {} + + requires-port@1.0.0: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.14.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + response-time@2.3.2: + dependencies: + depd: 1.1.2 + on-headers: 1.0.2 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + restore-cursor@4.0.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + retry@0.12.0: {} + + reusify@1.0.4: {} + + rfdc@1.4.1: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.59.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 + fsevents: 2.3.3 + + run-async@2.4.1: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@6.6.7: + dependencies: + tslib: 1.14.1 + + rxjs@7.8.1: + dependencies: + tslib: 2.6.3 + + safe-array-concat@1.1.2: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + isarray: 2.0.5 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-identifier@0.4.2: {} + + safe-regex-test@1.0.3: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-regex: 1.1.4 + + safe-stable-stringify@2.4.3: {} + + safer-buffer@2.1.2: {} + + sass-embedded-android-arm64@1.77.5: + optional: true + + sass-embedded-android-arm@1.77.5: + optional: true + + sass-embedded-android-ia32@1.77.5: + optional: true + + sass-embedded-android-x64@1.77.5: + optional: true + + sass-embedded-darwin-arm64@1.77.5: + optional: true + + sass-embedded-darwin-x64@1.77.5: + optional: true + + sass-embedded-linux-arm64@1.77.5: + optional: true + + sass-embedded-linux-arm@1.77.5: + optional: true + + sass-embedded-linux-ia32@1.77.5: + optional: true + + sass-embedded-linux-musl-arm64@1.77.5: + optional: true + + sass-embedded-linux-musl-arm@1.77.5: + optional: true + + sass-embedded-linux-musl-ia32@1.77.5: + optional: true + + sass-embedded-linux-musl-x64@1.77.5: + optional: true + + sass-embedded-linux-x64@1.77.5: + optional: true + + sass-embedded-win32-arm64@1.77.5: + optional: true + + sass-embedded-win32-ia32@1.77.5: + optional: true + + sass-embedded-win32-x64@1.77.5: + optional: true + + sass-embedded@1.77.5: + dependencies: + '@bufbuild/protobuf': 1.10.0 + buffer-builder: 0.2.0 + immutable: 4.3.6 + rxjs: 7.8.1 + supports-color: 8.1.1 + varint: 6.0.0 + optionalDependencies: + sass-embedded-android-arm: 1.77.5 + sass-embedded-android-arm64: 1.77.5 + sass-embedded-android-ia32: 1.77.5 + sass-embedded-android-x64: 1.77.5 + sass-embedded-darwin-arm64: 1.77.5 + sass-embedded-darwin-x64: 1.77.5 + sass-embedded-linux-arm: 1.77.5 + sass-embedded-linux-arm64: 1.77.5 + sass-embedded-linux-ia32: 1.77.5 + sass-embedded-linux-musl-arm: 1.77.5 + sass-embedded-linux-musl-arm64: 1.77.5 + sass-embedded-linux-musl-ia32: 1.77.5 + sass-embedded-linux-musl-x64: 1.77.5 + sass-embedded-linux-x64: 1.77.5 + sass-embedded-win32-arm64: 1.77.5 + sass-embedded-win32-ia32: 1.77.5 + sass-embedded-win32-x64: 1.77.5 + + sass@1.77.6: + dependencies: + chokidar: 3.6.0 + immutable: 4.3.6 + source-map-js: 1.2.0 + + saxes@5.0.1: + dependencies: + xmlchars: 2.2.0 + + semver@6.3.1: {} + + semver@7.6.2: {} + + send@0.18.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serialize-javascript@6.0.0: + dependencies: + randombytes: 2.1.0 + + serve-static@1.15.0: + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + + set-blocking@2.0.0: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.1: {} + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + + signal-exit@3.0.7: {} + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.6.2 + + sinon@14.0.2: + dependencies: + '@sinonjs/commons': 2.0.0 + '@sinonjs/fake-timers': 9.1.2 + '@sinonjs/samsam': 7.0.1 + diff: 5.2.0 + nise: 5.1.9 + supports-color: 7.2.0 + + slash@3.0.0: {} + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + sliced@1.0.1: {} + + smart-buffer@4.2.0: {} + + socket.io-adapter@2.5.5: + dependencies: + debug: 4.3.5(supports-color@5.5.0) + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-client@4.7.5: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.5(supports-color@5.5.0) + engine.io-client: 6.5.4 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.5(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + socket.io@4.7.5: + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.5 + debug: 4.3.5(supports-color@5.5.0) + engine.io: 6.5.5 + socket.io-adapter: 2.5.5 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socks-proxy-agent@7.0.0: + dependencies: + agent-base: 6.0.2 + debug: 4.3.5(supports-color@5.5.0) + socks: 2.8.3 + transitivePeerDependencies: + - supports-color + + socks@2.8.3: + dependencies: + ip-address: 9.0.5 + smart-buffer: 4.2.0 + + sort-keys@4.2.0: + dependencies: + is-plain-obj: 2.1.0 + + source-map-js@1.2.0: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: + optional: true + + spawn-command@0.0.2: {} + + sprintf-js@1.1.3: {} + + ssri@9.0.1: + dependencies: + minipass: 3.3.6 + + stack-trace@0.0.10: {} + + statuses@2.0.1: {} + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string.prototype.trim@1.2.9: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + string.prototype.trimend@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-bom@3.0.0: {} + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-final-newline@3.0.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: {} + + tapable@2.2.1: {} + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + tdigest@0.1.2: + dependencies: + bintrees: 1.0.2 + + text-hex@1.0.0: {} + + text-table@0.2.0: {} + + through@2.3.8: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyspawn@1.2.14: {} + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toastify-js@1.12.0: {} + + toidentifier@1.0.1: {} + + touch@3.1.1: {} + + tough-cookie@4.1.4: + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tr46@0.0.3: {} + + tr46@2.1.0: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + triple-beam@1.4.1: {} + + ts-node@10.9.2(@types/node@20.14.9)(typescript@5.5.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.14.9 + acorn: 8.12.0 + acorn-walk: 8.3.3 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.5.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@1.14.1: {} + + tslib@2.6.3: {} + + tsutils@3.21.0(typescript@5.5.3): + dependencies: + tslib: 1.14.1 + typescript: 5.5.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@0.6.0: {} + + type-fest@1.4.0: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typed-array-buffer@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 + + typed-array-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-byte-offset@1.0.2: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-length@1.0.6: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 + + typedarray-to-buffer@3.1.5: + dependencies: + is-typedarray: 1.0.0 + + typescript@5.5.3: {} + + unbox-primitive@1.0.2: + dependencies: + call-bind: 1.0.7 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + + undefsafe@2.0.5: {} + + undici-types@5.26.5: {} + + unique-filename@2.0.1: + dependencies: + unique-slug: 3.0.0 + + unique-slug@3.0.0: + dependencies: + imurmurhash: 0.1.4 + + universalify@0.2.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + url-value-parser@2.2.0: {} + + util-deprecate@1.0.2: {} + + utils-merge@1.0.1: {} + + v8-compile-cache-lib@3.0.1: {} + + varint@6.0.0: {} + + vary@1.1.2: {} + + vite@6.4.1(@types/node@20.14.9)(sass-embedded@1.77.5)(sass@1.77.6): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.8 + rollup: 4.59.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 20.14.9 + fsevents: 2.3.3 + sass: 1.77.6 + sass-embedded: 1.77.5 + + w3c-hr-time@1.0.2: + dependencies: + browser-process-hrtime: 1.0.0 + + w3c-xmlserializer@2.0.0: + dependencies: + xml-name-validator: 3.0.0 + + webidl-conversions@3.0.1: {} + + webidl-conversions@5.0.0: {} + + webidl-conversions@6.1.0: {} + + whatwg-encoding@1.0.5: + dependencies: + iconv-lite: 0.4.24 + + whatwg-mimetype@2.3.0: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + whatwg-url@8.7.0: + dependencies: + lodash: 4.17.21 + tr46: 2.1.0 + webidl-conversions: 6.1.0 + + which-boxed-primitive@1.0.2: + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + which-module@2.0.1: {} + + which-typed-array@1.1.15: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + + winston-transport@4.7.0: + dependencies: + logform: 2.6.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.13.0: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.3 + async: 3.2.5 + is-stream: 2.0.1 + logform: 2.6.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.4.3 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.7.0 + + word-wrap@1.2.5: {} + + workerpool@6.2.1: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + write-file-atomic@3.0.3: + dependencies: + imurmurhash: 0.1.4 + is-typedarray: 1.0.0 + signal-exit: 3.0.7 + typedarray-to-buffer: 3.1.5 + + write-json-file@4.3.0: + dependencies: + detect-indent: 6.1.0 + graceful-fs: 4.2.11 + is-plain-obj: 2.1.0 + make-dir: 3.1.0 + sort-keys: 4.2.0 + write-file-atomic: 3.0.3 + + ws@7.5.10: {} + + ws@8.17.1: {} + + xml-name-validator@3.0.0: {} + + xmlchars@2.2.0: {} + + xmlhttprequest-ssl@2.0.0: {} + + y18n@4.0.3: {} + + y18n@5.0.8: {} + + yallist@4.0.0: {} + + yaml@2.3.1: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs-parser@20.2.4: {} + + yargs-parser@21.1.1: {} + + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.4 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/wetty/public/favicon.ico b/wetty/public/favicon.ico new file mode 100644 index 0000000..fe2f47d Binary files /dev/null and b/wetty/public/favicon.ico differ diff --git a/wetty/public/xterm_config/functionality.js b/wetty/public/xterm_config/functionality.js new file mode 100644 index 0000000..4fde556 --- /dev/null +++ b/wetty/public/xterm_config/functionality.js @@ -0,0 +1,164 @@ +function optionGenericGet() { + return this.el.querySelector('input').value; +} +function optionGenericSet(value) { + this.el.querySelector('input').value = value; +} +function optionEnumGet() { + return this.el.querySelector('select').value; +} +function optionEnumSet(value) { + this.el.querySelector('select').value = value; +} +function optionBoolGet() { + return this.el.querySelector('input').checked; +} +function optionBoolSet(value) { + this.el.querySelector('input').checked = value; +} +function optionNumberGet() { + let value = (this.float === true ? parseFloat : parseInt)( + this.el.querySelector('input').value, + ); + if (Number.isNaN(value) || typeof value !== 'number') value = 0; + if (typeof this.min === 'number') value = Math.max(value, this.min); + if (typeof this.max === 'number') value = Math.min(value, this.max); + return value; +} +function optionNumberSet(value) { + this.el.querySelector('input').value = value; +} + +const allOptions = []; +/* eslint-disable @typescript-eslint/no-unused-vars */ + +function inflateOptions(optionsSchema) { + const booleanOption = document.querySelector('#boolean_option.templ'); + const enumOption = document.querySelector('#enum_option.templ'); + const textOption = document.querySelector('#text_option.templ'); + const numberOption = document.querySelector('#number_option.templ'); + const colorOption = document.querySelector('#color_option.templ'); + + function copyOver({ children }) { + while (children.length > 0) document.body.append(children[0]); + } + + optionsSchema.forEach(option => { + let el; + option.get = optionGenericGet.bind(option); + option.set = optionGenericSet.bind(option); + + switch (option.type) { + case 'boolean': + el = booleanOption.cloneNode(true); + option.get = optionBoolGet.bind(option); + option.set = optionBoolSet.bind(option); + break; + + case 'enum': + el = enumOption.cloneNode(true); + option.enum.forEach(varriant => { + const optionEl = document.createElement('option'); + optionEl.innerText = varriant; + optionEl.value = varriant; + el.querySelector('select').appendChild(optionEl); + }); + option.get = optionEnumGet.bind(option); + option.set = optionEnumSet.bind(option); + break; + + case 'text': + el = textOption.cloneNode(true); + break; + + case 'number': + el = numberOption.cloneNode(true); + if (option.float === true) + el.querySelector('input').setAttribute('step', '0.001'); + option.get = optionNumberGet.bind(option); + option.set = optionNumberSet.bind(option); + if (typeof option.min === 'number') + el.querySelector('input').setAttribute('min', option.min.toString()); + if (typeof option.max === 'number') + el.querySelector('input').setAttribute('max', option.max.toString()); + break; + + case 'color': + el = colorOption.cloneNode(true); + break; + + default: + throw new Error(`Unknown option type ${option.type}`); + } + + el.querySelector('.title').innerText = option.name; + el.querySelector('.desc').innerText = option.description; + [option.el] = el.children; + copyOver(el); + allOptions.push(option); + }); +} + +function getItem(json, path) { + const mypath = path[0]; + if (path.length === 1) return json[mypath]; + if (json[mypath] != null) return getItem(json[mypath], path.slice(1)); + return null; +} +function setItem(json, path, item) { + const mypath = path[0]; + if (path.length === 1) json[mypath] = item; + else { + if (json[mypath] == null) json[mypath] = {}; + setItem(json[mypath], path.slice(1), item); + } +} + +window.loadOptions = config => { + allOptions.forEach(option => { + let value = getItem(config, option.path); + if (option.nullable === true && option.type === 'text' && value == null) + value = null; + else if ( + option.nullable === true && + option.type === 'number' && + value == null + ) + value = -1; + else if (value == null) return; + if (option.json === true && option.type === 'text') + value = JSON.stringify(value); + option.set(value); + option.el.classList.remove('unbounded'); + }); +}; + +if (window.top === window) + // eslint-disable-next-line no-alert + alert( + 'Error: Page is top level. This page is supposed to be accessed from inside WeTTY.', + ); + +function saveConfig() { + const newConfig = {}; + allOptions.forEach(option => { + let newValue = option.get(); + if ( + option.nullable === true && + ((option.type === 'text' && newValue === '') || + (option.type === 'number' && newValue < 0)) + ) + return; + if (option.json === true && option.type === 'text') + newValue = JSON.parse(newValue); + setItem(newConfig, option.path, newValue); + }); + window.wetty_save_config(newConfig); +} + +window.addEventListener('input', () => { + const els = document.querySelectorAll('input, select'); + for (let i = 0; i < els.length; i += 1) { + els[i].addEventListener('input', saveConfig); + } +}); diff --git a/wetty/public/xterm_config/index.html b/wetty/public/xterm_config/index.html new file mode 100644 index 0000000..0bd91da --- /dev/null +++ b/wetty/public/xterm_config/index.html @@ -0,0 +1,71 @@ + + + + Wetty XTerm Configuration + + + +
+

Configure

+
+ +
+
+

+
+ +

+ +
+
+
+
+

+
+ +

+ +
+
+
+
+

+
+ +

+ +
+
+
+
+
+

+
+ +

+ +
+
+
+
+
+

+
+ +

+ +
+
+ + + +

General Options

+ +

Color Theme

+ +

Advanced XTerm Options

+ + + + + diff --git a/wetty/public/xterm_config/style.css b/wetty/public/xterm_config/style.css new file mode 100644 index 0000000..65003bd --- /dev/null +++ b/wetty/public/xterm_config/style.css @@ -0,0 +1,78 @@ +html { + background-color: black; +} +html, +body { + overflow: hidden auto; +} +body { + display: flex; + flex-flow: column nowrap; + font-family: monospace; + font-size: 1rem; + color: white; +} +.templ { + display: none; +} +h2 { + text-align: center; + text-decoration: underline; +} + +header { + display: flex; + flex-flow: row nowrap; + align-items: center; +} +header button { + padding: 0.5em; + font-size: 1em; + margin: 0.5em; + border-radius: 0.5em; +} + +.boolean_option, +.number_option, +.color_option, +.enum_option, +.text_option { + display: grid; + grid-template-columns: 100fr min(30em, 50%); + grid-template-rows: auto; + align-items: center; +} +.boolean_option input, +.number_option input, +.color_option input, +.text_option input, +.enum_option select { + margin: 0 0.5em; + font-size: 1em; + background-color: hsl(0, 0%, 20%); + color: white; + border: 2px solid white; +} + +.number_option input, +.text_option input, +.enum_option select { + padding: 0.4em; +} +.boolean_option input { + width: 2em; + height: 2em; + font-size: 0.75em; + justify-self: center; +} +.color_option input { + width: 100%; + height: 100%; + background-color: lightgray; +} + +.unbounded .title::before { + content: 'UNBOUND OPTION '; + color: red; + font-weight: bold; +} diff --git a/wetty/public/xterm_config/xterm_advanced_options.js b/wetty/public/xterm_config/xterm_advanced_options.js new file mode 100644 index 0000000..60fab15 --- /dev/null +++ b/wetty/public/xterm_config/xterm_advanced_options.js @@ -0,0 +1,127 @@ +window.inflateOptions([ + { + type: 'boolean', + name: 'Allow Proposed XTerm APIs', + description: + 'When set to false, any experimental/proposed APIs will throw errors.', + path: ['xterm', 'allowProposedApi'], + }, + { + type: 'boolean', + name: 'Allow Transparent Background', + description: 'Whether the background is allowed to be a non-opaque color.', + path: ['xterm', 'allowTransparency'], + }, + { + type: 'text', + name: 'Bell Sound URI', + description: 'URI for a custom bell character sound.', + path: ['xterm', 'bellSound'], + nullable: true, + }, + { + type: 'enum', + name: 'Bell Style', + description: 'How the terminal will react to the bell character', + path: ['xterm', 'bellStyle'], + enum: ['none', 'sound'], + }, + { + type: 'boolean', + name: 'Force End-Of-Line', + description: + 'When enabled, any new-line characters (\\n) will be interpreted as carriage-return new-line. (\\r\\n) Typically this is done by the shell program.', + path: ['xterm', 'convertEol'], + }, + { + type: 'boolean', + name: 'Disable Stdin', + description: 'Whether input should be disabled', + path: ['xterm', 'disableStdin'], + }, + { + type: 'number', + name: 'Letter Spacing', + description: 'The spacing in whole pixels between characters.', + path: ['xterm', 'letterSpacing'], + }, + { + type: 'number', + name: 'Line Height', + description: + 'Line height, multiplied by the font size to get the height of terminal rows.', + path: ['xterm', 'lineHeight'], + float: true, + }, + { + type: 'enum', + name: 'XTerm Log Level', + description: 'Log level for the XTerm library.', + path: ['xterm', 'logLevel'], + enum: ['debug', 'info', 'warn', 'error', 'off'], + }, + { + type: 'boolean', + name: 'Macintosh Option Key as Meta Key', + description: + 'When enabled, the Option key on Macs will be interpreted as the Meta key.', + path: ['xterm', 'macOptionIsMeta'], + }, + { + type: 'boolean', + name: 'Macintosh Option Click Forces Selection', + description: + "Whether holding a modifier key will force normal selection behavior, regardless of whether the terminal is in mouse events mode. This will also prevent mouse events from being emitted by the terminal. For example, this allows you to use xterm.js' regular selection inside tmux with mouse mode enabled.", + path: ['xterm', 'macOptionClickForcesSelection'], + }, + { + type: 'number', + name: 'Forced Contrast Ratio', + description: + 'Miminum contrast ratio for terminal text. This will alter the foreground color dynamically to ensure the ratio is met. Goes from 1 (do nothing) to 21 (strict black and white).', + path: ['xterm', 'minimumContrastRatio'], + float: true, + }, + { + type: 'enum', + name: 'Renderer Type', + description: + 'The terminal renderer to use. Canvas is preferred, but a DOM renderer is also available. Note: Letter spacing and cursor blink do not work in the DOM renderer.', + path: ['xterm', 'rendererType'], + enum: ['canvas', 'dom'], + }, + { + type: 'boolean', + name: 'Right Click Selects Words', + description: 'Whether to select the word under the cursor on right click.', + path: ['xterm', 'rightClickSelectsWord'], + }, + { + type: 'boolean', + name: 'Screen Reader Support', + description: + 'Whether screen reader support is enabled. When on this will expose supporting elements in the DOM to support NVDA on Windows and VoiceOver on macOS.', + path: ['xterm', 'screenReaderMode'], + }, + { + type: 'number', + name: 'Tab Stop Width', + description: 'The size of tab stops in the terminal.', + path: ['xterm', 'tabStopWidth'], + }, + { + type: 'boolean', + name: 'Windows Mode', + description: + "\"Whether 'Windows mode' is enabled. Because Windows backends winpty and conpty operate by doing line wrapping on their side, xterm.js does not have access to wrapped lines. When Windows mode is enabled the following changes will be in effect:\n- Reflow is disabled.\n- Lines are assumed to be wrapped if the last character of the line is not whitespace.", + path: ['xterm', 'windowsMode'], + }, + { + type: 'text', + name: 'Word Separator', + description: + 'All characters considered word separators. Used for double-click to select word logic. Encoded as JSON in this editor for editing convienience.', + path: ['xterm', 'wordSeparator'], + json: true, + }, +]); diff --git a/wetty/public/xterm_config/xterm_color_theme.js b/wetty/public/xterm_config/xterm_color_theme.js new file mode 100644 index 0000000..a58cb89 --- /dev/null +++ b/wetty/public/xterm_config/xterm_color_theme.js @@ -0,0 +1,152 @@ +const selectionColorOption = { + type: 'color', + name: 'Selection Color', + description: 'Background color for selected text. Can be transparent.', + path: ['xterm', 'theme', 'selection'], +}; +const selectionColorOpacityOption = { + type: 'number', + name: 'Selection Color Opacity', + description: + 'Opacity of the selection highlight. A value between 1 (fully opaque) and 0 (fully transparent).', + path: ['wettyVoid'], + float: true, + min: 0, + max: 1, +}; + +window.inflateOptions([ + { + type: 'color', + name: 'Foreground Color', + description: 'The default foreground (text) color.', + path: ['xterm', 'theme', 'foreground'], + }, + { + type: 'color', + name: 'Background Color', + description: 'The default background color.', + path: ['xterm', 'theme', 'background'], + }, + { + type: 'color', + name: 'Cursor Color', + description: 'Color of the cursor.', + path: ['xterm', 'theme', 'cursor'], + }, + { + type: 'color', + name: 'Block Cursor Accent Color', + description: + 'The accent color of the cursor, used as the foreground color for block cursors.', + path: ['xterm', 'theme', 'cursorAccent'], + }, + selectionColorOption, + selectionColorOpacityOption, + { + type: 'color', + name: 'Black', + description: 'Color for ANSI Black text.', + path: ['xterm', 'theme', 'black'], + }, + { + type: 'color', + name: 'Red', + description: 'Color for ANSI Red text.', + path: ['xterm', 'theme', 'red'], + }, + { + type: 'color', + name: 'Green', + description: 'Color for ANSI Green text.', + path: ['xterm', 'theme', 'green'], + }, + { + type: 'color', + name: 'Yellow', + description: 'Color for ANSI Yellow text.', + path: ['xterm', 'theme', 'yellow'], + }, + { + type: 'color', + name: 'Blue', + description: 'Color for ANSI Blue text.', + path: ['xterm', 'theme', 'blue'], + }, + { + type: 'color', + name: 'Magenta', + description: 'Color for ANSI Magenta text.', + path: ['xterm', 'theme', 'magenta'], + }, + { + type: 'color', + name: 'Cyan', + description: 'Color for ANSI Cyan text.', + path: ['xterm', 'theme', 'cyan'], + }, + { + type: 'color', + name: 'White', + description: 'Color for ANSI White text.', + path: ['xterm', 'theme', 'white'], + }, + { + type: 'color', + name: 'Bright Black', + description: 'Color for ANSI Bright Black text.', + path: ['xterm', 'theme', 'brightBlack'], + }, + { + type: 'color', + name: 'Bright Red', + description: 'Color for ANSI Bright Red text.', + path: ['xterm', 'theme', 'brightRed'], + }, + { + type: 'color', + name: 'Bright Green', + description: 'Color for ANSI Bright Green text.', + path: ['xterm', 'theme', 'brightGreen'], + }, + { + type: 'color', + name: 'Bright Yellow', + description: 'Color for ANSI Bright Yellow text.', + path: ['xterm', 'theme', 'brightYellow'], + }, + { + type: 'color', + name: 'Bright Blue', + description: 'Color for ANSI Bright Blue text.', + path: ['xterm', 'theme', 'brightBlue'], + }, + { + type: 'color', + name: 'Bright Magenta', + description: 'Color for ANSI Bright Magenta text.', + path: ['xterm', 'theme', 'brightMagenta'], + }, + { + type: 'color', + name: 'Bright White', + description: 'Color for ANSI Bright White text.', + path: ['xterm', 'theme', 'brightWhite'], + }, +]); + +selectionColorOption.get = function getInput() { + return ( + this.el.querySelector('input').value + + Math.round( + selectionColorOpacityOption.el.querySelector('input').value * 255, + ).toString(16) + ); +}; +selectionColorOption.set = function setInput(value) { + this.el.querySelector('input').value = value.substring(0, 7); + selectionColorOpacityOption.el.querySelector('input').value = + Math.round((parseInt(value.substring(7), 16) / 255) * 100) / 100; +}; +selectionColorOpacityOption.get = () => 0; +selectionColorOpacityOption.set = () => 0; diff --git a/wetty/public/xterm_config/xterm_defaults.js b/wetty/public/xterm_config/xterm_defaults.js new file mode 100644 index 0000000..cf5df70 --- /dev/null +++ b/wetty/public/xterm_config/xterm_defaults.js @@ -0,0 +1,70 @@ +const DEFAULT_BELL_SOUND = + 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; +window.loadOptions({ + wettyFitTerminal: true, + wettyVoid: 0, + + xterm: { + cols: 80, + rows: 24, + cursorBlink: false, + cursorStyle: 'block', + cursorWidth: 1, + bellSound: DEFAULT_BELL_SOUND, + bellStyle: 'none', + drawBoldTextInBrightColors: true, + fastScrollModifier: 'alt', + fastScrollSensitivity: 5, + fontFamily: 'courier-new, courier, monospace', + fontSize: 15, + fontWeight: 'normal', + fontWeightBold: 'bold', + lineHeight: 1.0, + linkTooltipHoverDuration: 500, + letterSpacing: 0, + logLevel: 'info', + scrollback: 1000, + scrollSensitivity: 1, + screenReaderMode: false, + macOptionIsMeta: false, + macOptionClickForcesSelection: false, + minimumContrastRatio: 1, + disableStdin: false, + allowProposedApi: true, + allowTransparency: false, + tabStopWidth: 8, + rightClickSelectsWord: false, + rendererType: 'canvas', + windowOptions: {}, + windowsMode: false, + wordSeparator: ' ()[]{}\',"`', + convertEol: false, + termName: 'xterm', + cancelEvents: false, + + theme: { + foreground: '#ffffff', + background: '#000000', + cursor: '#ffffff', + cursorAccent: '#000000', + selection: '#FFFFFF4D', + + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec', + }, + }, +}); diff --git a/wetty/public/xterm_config/xterm_general_options.js b/wetty/public/xterm_config/xterm_general_options.js new file mode 100644 index 0000000..dd9e2ed --- /dev/null +++ b/wetty/public/xterm_config/xterm_general_options.js @@ -0,0 +1,136 @@ +window.inflateOptions([ + { + type: 'text', + name: 'Font Family', + description: 'The font family for terminal text.', + path: ['xterm', 'fontFamily'], + }, + { + type: 'number', + name: 'Font Size', + description: 'The font size in CSS pixels for terminal text.', + path: ['xterm', 'fontSize'], + min: 4, + }, + { + type: 'enum', + name: 'Regular Font Weight', + description: 'The font weight for non-bold text.', + path: ['xterm', 'fontWeight'], + enum: [ + 'normal', + 'bold', + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900', + ], + }, + { + type: 'enum', + name: 'Bold Font Weight', + description: 'The font weight for bold text.', + path: ['xterm', 'fontWeightBold'], + enum: [ + 'normal', + 'bold', + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900', + ], + }, + { + type: 'boolean', + name: 'Fit Terminal', + description: + 'Automatically fits the terminal to the page, overriding terminal columns and rows.', + path: ['wettyFitTerminal'], + }, + { + type: 'number', + name: 'Terminal Columns', + description: + 'The number of columns in the terminal. Overridden by the Fit Terminal option.', + path: ['xterm', 'cols'], + nullable: true, + }, + { + type: 'number', + name: 'Terminal Rows', + description: + 'The number of rows in the terminal. Overridden by the Fit Terminal option.', + path: ['xterm', 'rows'], + nullable: true, + }, + { + type: 'enum', + name: 'Cursor Style', + description: 'The style of the cursor', + path: ['xterm', 'cursorStyle'], + enum: ['block', 'underline', 'bar'], + }, + { + type: 'boolean', + name: 'Blinking Cursor', + description: 'Whether the cursor blinks', + path: ['xterm', 'cursorBlink'], + }, + { + type: 'number', + name: 'Bar Cursor Width', + description: + "The width of the cursor in CSS pixels. Only applies when Cursor Style is set to 'bar'.", + path: ['xterm', 'cursorWidth'], + }, + { + type: 'boolean', + name: 'Draw Bold Text In Bright Colors', + description: 'Whether to draw bold text in bright colors', + path: ['xterm', 'drawBoldTextInBrightColors'], + }, + { + type: 'number', + name: 'Scroll Sensitivity', + description: 'The scroll speed multiplier for regular scrolling.', + path: ['xterm', 'scrollSensitivity'], + float: true, + }, + { + type: 'enum', + name: 'Fast Scroll Key', + description: 'The modifier key to hold to multiply scroll speed.', + path: ['xterm', 'fastScrollModifier'], + enum: ['none', 'alt', 'shift', 'ctrl'], + }, + { + type: 'number', + name: 'Fast Scroll Multiplier', + description: 'The scroll speed multiplier used for fast scrolling.', + path: ['xterm', 'fastScrollSensitivity'], + float: true, + }, + { + type: 'number', + name: 'Scrollback Rows', + description: + 'The amount of scrollback rows, rows you can scroll up to after they leave the viewport, to keep.', + path: ['xterm', 'scrollback'], + }, + { + type: 'number', + name: 'Tab Stop Width', + description: 'The size of tab stops in the terminal.', + path: ['xterm', 'tabStopWidth'], + }, +]); diff --git a/wetty/src/assets/favicon.ico b/wetty/src/assets/favicon.ico new file mode 100644 index 0000000..fe2f47d Binary files /dev/null and b/wetty/src/assets/favicon.ico differ diff --git a/wetty/src/assets/scss/options.scss b/wetty/src/assets/scss/options.scss new file mode 100644 index 0000000..e75dd4b --- /dev/null +++ b/wetty/src/assets/scss/options.scss @@ -0,0 +1,111 @@ +@use './variables'; + +#options { + height: 16px; + position: absolute; + right: 1em; + top: 1em; + width: 16px; + z-index: 20; + + .toggler { + color: variables.$lgrey; + display: inline-block; + font-size: 16px; + position: absolute; + right: 1em; + top: 0; + z-index: 20; + + :hover { + color: variables.$white; + } + } + + .editor { + background-color: rgba(0, 0, 0, 0.85); + border-color: rgba(255, 255, 255, 0.25); + border-radius: 0.3em; + color: #eee; + display: none; + font-size: 24px; + height: 100%; + padding: 0.5em; + position: relative; + right: 2em; + top: 1em; + width: 100%; + } +} + +#options.opened { + height: max(min(300px, 75vh), 50vh); + width: max(min(500px, 75vw), 50vw); + + .editor { + display: flex; + } + + .error { + color: red; + } +} + +#functions { + position: fixed; + right: 2em; + top: 6em; + z-index: 20; + + > a { + padding: 10px; + position: absolute; + right: -10px; + top: -40px; + color: variables.$lgrey; + + :hover { + color: variables.$white; + } + } + + .onscreen-buttons { + display: none; + width: 200px; + height: 200px; + border: solid 2px rgba(255, 255, 255, 0.25); + border-radius: 0.3em; + background-color: rgba(0, 0, 0, 0.85); + > a { + bottom: 1em; + right: 2em; + text-decoration: none; + color: white; + > div { + padding: 5px; + outline: 2px solid white; + margin: 10px; + display: inline-block; + font-weight: bold; + border-radius: 10px; + } + } + } + + .active { + display: block; + } + + .onscreen-buttons > a:active { + > div { + background-color: rgba(255, 255, 255, 0.25); + } + } + + #onscreen-ctrl.active { + display: inline-block; + > div { + background-color: lightgray; + } + } +} diff --git a/wetty/src/assets/scss/overlay.scss b/wetty/src/assets/scss/overlay.scss new file mode 100644 index 0000000..e887254 --- /dev/null +++ b/wetty/src/assets/scss/overlay.scss @@ -0,0 +1,28 @@ +@use './variables'; + +#overlay { + background-color: variables.$grey; + display: none; + height: 100%; + position: absolute; + width: 100%; + z-index: 100; + + .error { + display: flex; + flex-direction: column; + height: 100%; + justify-content: center; + width: 100%; + + #msg { + align-self: center; + color: variables.$white; + } + + input { + align-self: center; + margin: 16px; + } + } +} diff --git a/wetty/src/assets/scss/styles.scss b/wetty/src/assets/scss/styles.scss new file mode 100644 index 0000000..c161768 --- /dev/null +++ b/wetty/src/assets/scss/styles.scss @@ -0,0 +1,25 @@ +@use '@xterm/xterm/css/xterm.css'; +@use 'toastify-js/src/toastify.css'; +@use './variables'; +@use './overlay'; +@use './options'; +@use './terminal'; + +html, +body { + background-color: variables.$black; + height: 100%; + margin: 0; + overflow: hidden; + + .toastify { + border-radius: 0; + color: variables.$black; + } +} + +.xterm { + .xterm-viewport { + overflow-y: hidden; + } +} diff --git a/wetty/src/assets/scss/terminal.scss b/wetty/src/assets/scss/terminal.scss new file mode 100644 index 0000000..c447840 --- /dev/null +++ b/wetty/src/assets/scss/terminal.scss @@ -0,0 +1,6 @@ +#terminal { + display: flex; + height: 100%; + position: relative; + width: 100%; +} diff --git a/wetty/src/assets/scss/variables.scss b/wetty/src/assets/scss/variables.scss new file mode 100644 index 0000000..7467452 --- /dev/null +++ b/wetty/src/assets/scss/variables.scss @@ -0,0 +1,4 @@ +$black: #000; +$grey: rgba(0, 0, 0, 0.75); +$white: #fff; +$lgrey: #ccc; diff --git a/wetty/src/assets/xterm_config/functionality.js b/wetty/src/assets/xterm_config/functionality.js new file mode 100644 index 0000000..4fde556 --- /dev/null +++ b/wetty/src/assets/xterm_config/functionality.js @@ -0,0 +1,164 @@ +function optionGenericGet() { + return this.el.querySelector('input').value; +} +function optionGenericSet(value) { + this.el.querySelector('input').value = value; +} +function optionEnumGet() { + return this.el.querySelector('select').value; +} +function optionEnumSet(value) { + this.el.querySelector('select').value = value; +} +function optionBoolGet() { + return this.el.querySelector('input').checked; +} +function optionBoolSet(value) { + this.el.querySelector('input').checked = value; +} +function optionNumberGet() { + let value = (this.float === true ? parseFloat : parseInt)( + this.el.querySelector('input').value, + ); + if (Number.isNaN(value) || typeof value !== 'number') value = 0; + if (typeof this.min === 'number') value = Math.max(value, this.min); + if (typeof this.max === 'number') value = Math.min(value, this.max); + return value; +} +function optionNumberSet(value) { + this.el.querySelector('input').value = value; +} + +const allOptions = []; +/* eslint-disable @typescript-eslint/no-unused-vars */ + +function inflateOptions(optionsSchema) { + const booleanOption = document.querySelector('#boolean_option.templ'); + const enumOption = document.querySelector('#enum_option.templ'); + const textOption = document.querySelector('#text_option.templ'); + const numberOption = document.querySelector('#number_option.templ'); + const colorOption = document.querySelector('#color_option.templ'); + + function copyOver({ children }) { + while (children.length > 0) document.body.append(children[0]); + } + + optionsSchema.forEach(option => { + let el; + option.get = optionGenericGet.bind(option); + option.set = optionGenericSet.bind(option); + + switch (option.type) { + case 'boolean': + el = booleanOption.cloneNode(true); + option.get = optionBoolGet.bind(option); + option.set = optionBoolSet.bind(option); + break; + + case 'enum': + el = enumOption.cloneNode(true); + option.enum.forEach(varriant => { + const optionEl = document.createElement('option'); + optionEl.innerText = varriant; + optionEl.value = varriant; + el.querySelector('select').appendChild(optionEl); + }); + option.get = optionEnumGet.bind(option); + option.set = optionEnumSet.bind(option); + break; + + case 'text': + el = textOption.cloneNode(true); + break; + + case 'number': + el = numberOption.cloneNode(true); + if (option.float === true) + el.querySelector('input').setAttribute('step', '0.001'); + option.get = optionNumberGet.bind(option); + option.set = optionNumberSet.bind(option); + if (typeof option.min === 'number') + el.querySelector('input').setAttribute('min', option.min.toString()); + if (typeof option.max === 'number') + el.querySelector('input').setAttribute('max', option.max.toString()); + break; + + case 'color': + el = colorOption.cloneNode(true); + break; + + default: + throw new Error(`Unknown option type ${option.type}`); + } + + el.querySelector('.title').innerText = option.name; + el.querySelector('.desc').innerText = option.description; + [option.el] = el.children; + copyOver(el); + allOptions.push(option); + }); +} + +function getItem(json, path) { + const mypath = path[0]; + if (path.length === 1) return json[mypath]; + if (json[mypath] != null) return getItem(json[mypath], path.slice(1)); + return null; +} +function setItem(json, path, item) { + const mypath = path[0]; + if (path.length === 1) json[mypath] = item; + else { + if (json[mypath] == null) json[mypath] = {}; + setItem(json[mypath], path.slice(1), item); + } +} + +window.loadOptions = config => { + allOptions.forEach(option => { + let value = getItem(config, option.path); + if (option.nullable === true && option.type === 'text' && value == null) + value = null; + else if ( + option.nullable === true && + option.type === 'number' && + value == null + ) + value = -1; + else if (value == null) return; + if (option.json === true && option.type === 'text') + value = JSON.stringify(value); + option.set(value); + option.el.classList.remove('unbounded'); + }); +}; + +if (window.top === window) + // eslint-disable-next-line no-alert + alert( + 'Error: Page is top level. This page is supposed to be accessed from inside WeTTY.', + ); + +function saveConfig() { + const newConfig = {}; + allOptions.forEach(option => { + let newValue = option.get(); + if ( + option.nullable === true && + ((option.type === 'text' && newValue === '') || + (option.type === 'number' && newValue < 0)) + ) + return; + if (option.json === true && option.type === 'text') + newValue = JSON.parse(newValue); + setItem(newConfig, option.path, newValue); + }); + window.wetty_save_config(newConfig); +} + +window.addEventListener('input', () => { + const els = document.querySelectorAll('input, select'); + for (let i = 0; i < els.length; i += 1) { + els[i].addEventListener('input', saveConfig); + } +}); diff --git a/wetty/src/assets/xterm_config/index.html b/wetty/src/assets/xterm_config/index.html new file mode 100644 index 0000000..0bd91da --- /dev/null +++ b/wetty/src/assets/xterm_config/index.html @@ -0,0 +1,71 @@ + + + + Wetty XTerm Configuration + + + +
+

Configure

+
+ +
+
+

+
+ +

+ +
+
+
+
+

+
+ +

+ +
+
+
+
+

+
+ +

+ +
+
+
+
+
+

+
+ +

+ +
+
+
+
+
+

+
+ +

+ +
+
+ + + +

General Options

+ +

Color Theme

+ +

Advanced XTerm Options

+ + + + + diff --git a/wetty/src/assets/xterm_config/style.css b/wetty/src/assets/xterm_config/style.css new file mode 100644 index 0000000..65003bd --- /dev/null +++ b/wetty/src/assets/xterm_config/style.css @@ -0,0 +1,78 @@ +html { + background-color: black; +} +html, +body { + overflow: hidden auto; +} +body { + display: flex; + flex-flow: column nowrap; + font-family: monospace; + font-size: 1rem; + color: white; +} +.templ { + display: none; +} +h2 { + text-align: center; + text-decoration: underline; +} + +header { + display: flex; + flex-flow: row nowrap; + align-items: center; +} +header button { + padding: 0.5em; + font-size: 1em; + margin: 0.5em; + border-radius: 0.5em; +} + +.boolean_option, +.number_option, +.color_option, +.enum_option, +.text_option { + display: grid; + grid-template-columns: 100fr min(30em, 50%); + grid-template-rows: auto; + align-items: center; +} +.boolean_option input, +.number_option input, +.color_option input, +.text_option input, +.enum_option select { + margin: 0 0.5em; + font-size: 1em; + background-color: hsl(0, 0%, 20%); + color: white; + border: 2px solid white; +} + +.number_option input, +.text_option input, +.enum_option select { + padding: 0.4em; +} +.boolean_option input { + width: 2em; + height: 2em; + font-size: 0.75em; + justify-self: center; +} +.color_option input { + width: 100%; + height: 100%; + background-color: lightgray; +} + +.unbounded .title::before { + content: 'UNBOUND OPTION '; + color: red; + font-weight: bold; +} diff --git a/wetty/src/assets/xterm_config/xterm_advanced_options.js b/wetty/src/assets/xterm_config/xterm_advanced_options.js new file mode 100644 index 0000000..60fab15 --- /dev/null +++ b/wetty/src/assets/xterm_config/xterm_advanced_options.js @@ -0,0 +1,127 @@ +window.inflateOptions([ + { + type: 'boolean', + name: 'Allow Proposed XTerm APIs', + description: + 'When set to false, any experimental/proposed APIs will throw errors.', + path: ['xterm', 'allowProposedApi'], + }, + { + type: 'boolean', + name: 'Allow Transparent Background', + description: 'Whether the background is allowed to be a non-opaque color.', + path: ['xterm', 'allowTransparency'], + }, + { + type: 'text', + name: 'Bell Sound URI', + description: 'URI for a custom bell character sound.', + path: ['xterm', 'bellSound'], + nullable: true, + }, + { + type: 'enum', + name: 'Bell Style', + description: 'How the terminal will react to the bell character', + path: ['xterm', 'bellStyle'], + enum: ['none', 'sound'], + }, + { + type: 'boolean', + name: 'Force End-Of-Line', + description: + 'When enabled, any new-line characters (\\n) will be interpreted as carriage-return new-line. (\\r\\n) Typically this is done by the shell program.', + path: ['xterm', 'convertEol'], + }, + { + type: 'boolean', + name: 'Disable Stdin', + description: 'Whether input should be disabled', + path: ['xterm', 'disableStdin'], + }, + { + type: 'number', + name: 'Letter Spacing', + description: 'The spacing in whole pixels between characters.', + path: ['xterm', 'letterSpacing'], + }, + { + type: 'number', + name: 'Line Height', + description: + 'Line height, multiplied by the font size to get the height of terminal rows.', + path: ['xterm', 'lineHeight'], + float: true, + }, + { + type: 'enum', + name: 'XTerm Log Level', + description: 'Log level for the XTerm library.', + path: ['xterm', 'logLevel'], + enum: ['debug', 'info', 'warn', 'error', 'off'], + }, + { + type: 'boolean', + name: 'Macintosh Option Key as Meta Key', + description: + 'When enabled, the Option key on Macs will be interpreted as the Meta key.', + path: ['xterm', 'macOptionIsMeta'], + }, + { + type: 'boolean', + name: 'Macintosh Option Click Forces Selection', + description: + "Whether holding a modifier key will force normal selection behavior, regardless of whether the terminal is in mouse events mode. This will also prevent mouse events from being emitted by the terminal. For example, this allows you to use xterm.js' regular selection inside tmux with mouse mode enabled.", + path: ['xterm', 'macOptionClickForcesSelection'], + }, + { + type: 'number', + name: 'Forced Contrast Ratio', + description: + 'Miminum contrast ratio for terminal text. This will alter the foreground color dynamically to ensure the ratio is met. Goes from 1 (do nothing) to 21 (strict black and white).', + path: ['xterm', 'minimumContrastRatio'], + float: true, + }, + { + type: 'enum', + name: 'Renderer Type', + description: + 'The terminal renderer to use. Canvas is preferred, but a DOM renderer is also available. Note: Letter spacing and cursor blink do not work in the DOM renderer.', + path: ['xterm', 'rendererType'], + enum: ['canvas', 'dom'], + }, + { + type: 'boolean', + name: 'Right Click Selects Words', + description: 'Whether to select the word under the cursor on right click.', + path: ['xterm', 'rightClickSelectsWord'], + }, + { + type: 'boolean', + name: 'Screen Reader Support', + description: + 'Whether screen reader support is enabled. When on this will expose supporting elements in the DOM to support NVDA on Windows and VoiceOver on macOS.', + path: ['xterm', 'screenReaderMode'], + }, + { + type: 'number', + name: 'Tab Stop Width', + description: 'The size of tab stops in the terminal.', + path: ['xterm', 'tabStopWidth'], + }, + { + type: 'boolean', + name: 'Windows Mode', + description: + "\"Whether 'Windows mode' is enabled. Because Windows backends winpty and conpty operate by doing line wrapping on their side, xterm.js does not have access to wrapped lines. When Windows mode is enabled the following changes will be in effect:\n- Reflow is disabled.\n- Lines are assumed to be wrapped if the last character of the line is not whitespace.", + path: ['xterm', 'windowsMode'], + }, + { + type: 'text', + name: 'Word Separator', + description: + 'All characters considered word separators. Used for double-click to select word logic. Encoded as JSON in this editor for editing convienience.', + path: ['xterm', 'wordSeparator'], + json: true, + }, +]); diff --git a/wetty/src/assets/xterm_config/xterm_color_theme.js b/wetty/src/assets/xterm_config/xterm_color_theme.js new file mode 100644 index 0000000..a58cb89 --- /dev/null +++ b/wetty/src/assets/xterm_config/xterm_color_theme.js @@ -0,0 +1,152 @@ +const selectionColorOption = { + type: 'color', + name: 'Selection Color', + description: 'Background color for selected text. Can be transparent.', + path: ['xterm', 'theme', 'selection'], +}; +const selectionColorOpacityOption = { + type: 'number', + name: 'Selection Color Opacity', + description: + 'Opacity of the selection highlight. A value between 1 (fully opaque) and 0 (fully transparent).', + path: ['wettyVoid'], + float: true, + min: 0, + max: 1, +}; + +window.inflateOptions([ + { + type: 'color', + name: 'Foreground Color', + description: 'The default foreground (text) color.', + path: ['xterm', 'theme', 'foreground'], + }, + { + type: 'color', + name: 'Background Color', + description: 'The default background color.', + path: ['xterm', 'theme', 'background'], + }, + { + type: 'color', + name: 'Cursor Color', + description: 'Color of the cursor.', + path: ['xterm', 'theme', 'cursor'], + }, + { + type: 'color', + name: 'Block Cursor Accent Color', + description: + 'The accent color of the cursor, used as the foreground color for block cursors.', + path: ['xterm', 'theme', 'cursorAccent'], + }, + selectionColorOption, + selectionColorOpacityOption, + { + type: 'color', + name: 'Black', + description: 'Color for ANSI Black text.', + path: ['xterm', 'theme', 'black'], + }, + { + type: 'color', + name: 'Red', + description: 'Color for ANSI Red text.', + path: ['xterm', 'theme', 'red'], + }, + { + type: 'color', + name: 'Green', + description: 'Color for ANSI Green text.', + path: ['xterm', 'theme', 'green'], + }, + { + type: 'color', + name: 'Yellow', + description: 'Color for ANSI Yellow text.', + path: ['xterm', 'theme', 'yellow'], + }, + { + type: 'color', + name: 'Blue', + description: 'Color for ANSI Blue text.', + path: ['xterm', 'theme', 'blue'], + }, + { + type: 'color', + name: 'Magenta', + description: 'Color for ANSI Magenta text.', + path: ['xterm', 'theme', 'magenta'], + }, + { + type: 'color', + name: 'Cyan', + description: 'Color for ANSI Cyan text.', + path: ['xterm', 'theme', 'cyan'], + }, + { + type: 'color', + name: 'White', + description: 'Color for ANSI White text.', + path: ['xterm', 'theme', 'white'], + }, + { + type: 'color', + name: 'Bright Black', + description: 'Color for ANSI Bright Black text.', + path: ['xterm', 'theme', 'brightBlack'], + }, + { + type: 'color', + name: 'Bright Red', + description: 'Color for ANSI Bright Red text.', + path: ['xterm', 'theme', 'brightRed'], + }, + { + type: 'color', + name: 'Bright Green', + description: 'Color for ANSI Bright Green text.', + path: ['xterm', 'theme', 'brightGreen'], + }, + { + type: 'color', + name: 'Bright Yellow', + description: 'Color for ANSI Bright Yellow text.', + path: ['xterm', 'theme', 'brightYellow'], + }, + { + type: 'color', + name: 'Bright Blue', + description: 'Color for ANSI Bright Blue text.', + path: ['xterm', 'theme', 'brightBlue'], + }, + { + type: 'color', + name: 'Bright Magenta', + description: 'Color for ANSI Bright Magenta text.', + path: ['xterm', 'theme', 'brightMagenta'], + }, + { + type: 'color', + name: 'Bright White', + description: 'Color for ANSI Bright White text.', + path: ['xterm', 'theme', 'brightWhite'], + }, +]); + +selectionColorOption.get = function getInput() { + return ( + this.el.querySelector('input').value + + Math.round( + selectionColorOpacityOption.el.querySelector('input').value * 255, + ).toString(16) + ); +}; +selectionColorOption.set = function setInput(value) { + this.el.querySelector('input').value = value.substring(0, 7); + selectionColorOpacityOption.el.querySelector('input').value = + Math.round((parseInt(value.substring(7), 16) / 255) * 100) / 100; +}; +selectionColorOpacityOption.get = () => 0; +selectionColorOpacityOption.set = () => 0; diff --git a/wetty/src/assets/xterm_config/xterm_defaults.js b/wetty/src/assets/xterm_config/xterm_defaults.js new file mode 100644 index 0000000..cf5df70 --- /dev/null +++ b/wetty/src/assets/xterm_config/xterm_defaults.js @@ -0,0 +1,70 @@ +const DEFAULT_BELL_SOUND = + 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; +window.loadOptions({ + wettyFitTerminal: true, + wettyVoid: 0, + + xterm: { + cols: 80, + rows: 24, + cursorBlink: false, + cursorStyle: 'block', + cursorWidth: 1, + bellSound: DEFAULT_BELL_SOUND, + bellStyle: 'none', + drawBoldTextInBrightColors: true, + fastScrollModifier: 'alt', + fastScrollSensitivity: 5, + fontFamily: 'courier-new, courier, monospace', + fontSize: 15, + fontWeight: 'normal', + fontWeightBold: 'bold', + lineHeight: 1.0, + linkTooltipHoverDuration: 500, + letterSpacing: 0, + logLevel: 'info', + scrollback: 1000, + scrollSensitivity: 1, + screenReaderMode: false, + macOptionIsMeta: false, + macOptionClickForcesSelection: false, + minimumContrastRatio: 1, + disableStdin: false, + allowProposedApi: true, + allowTransparency: false, + tabStopWidth: 8, + rightClickSelectsWord: false, + rendererType: 'canvas', + windowOptions: {}, + windowsMode: false, + wordSeparator: ' ()[]{}\',"`', + convertEol: false, + termName: 'xterm', + cancelEvents: false, + + theme: { + foreground: '#ffffff', + background: '#000000', + cursor: '#ffffff', + cursorAccent: '#000000', + selection: '#FFFFFF4D', + + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec', + }, + }, +}); diff --git a/wetty/src/assets/xterm_config/xterm_general_options.js b/wetty/src/assets/xterm_config/xterm_general_options.js new file mode 100644 index 0000000..dd9e2ed --- /dev/null +++ b/wetty/src/assets/xterm_config/xterm_general_options.js @@ -0,0 +1,136 @@ +window.inflateOptions([ + { + type: 'text', + name: 'Font Family', + description: 'The font family for terminal text.', + path: ['xterm', 'fontFamily'], + }, + { + type: 'number', + name: 'Font Size', + description: 'The font size in CSS pixels for terminal text.', + path: ['xterm', 'fontSize'], + min: 4, + }, + { + type: 'enum', + name: 'Regular Font Weight', + description: 'The font weight for non-bold text.', + path: ['xterm', 'fontWeight'], + enum: [ + 'normal', + 'bold', + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900', + ], + }, + { + type: 'enum', + name: 'Bold Font Weight', + description: 'The font weight for bold text.', + path: ['xterm', 'fontWeightBold'], + enum: [ + 'normal', + 'bold', + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900', + ], + }, + { + type: 'boolean', + name: 'Fit Terminal', + description: + 'Automatically fits the terminal to the page, overriding terminal columns and rows.', + path: ['wettyFitTerminal'], + }, + { + type: 'number', + name: 'Terminal Columns', + description: + 'The number of columns in the terminal. Overridden by the Fit Terminal option.', + path: ['xterm', 'cols'], + nullable: true, + }, + { + type: 'number', + name: 'Terminal Rows', + description: + 'The number of rows in the terminal. Overridden by the Fit Terminal option.', + path: ['xterm', 'rows'], + nullable: true, + }, + { + type: 'enum', + name: 'Cursor Style', + description: 'The style of the cursor', + path: ['xterm', 'cursorStyle'], + enum: ['block', 'underline', 'bar'], + }, + { + type: 'boolean', + name: 'Blinking Cursor', + description: 'Whether the cursor blinks', + path: ['xterm', 'cursorBlink'], + }, + { + type: 'number', + name: 'Bar Cursor Width', + description: + "The width of the cursor in CSS pixels. Only applies when Cursor Style is set to 'bar'.", + path: ['xterm', 'cursorWidth'], + }, + { + type: 'boolean', + name: 'Draw Bold Text In Bright Colors', + description: 'Whether to draw bold text in bright colors', + path: ['xterm', 'drawBoldTextInBrightColors'], + }, + { + type: 'number', + name: 'Scroll Sensitivity', + description: 'The scroll speed multiplier for regular scrolling.', + path: ['xterm', 'scrollSensitivity'], + float: true, + }, + { + type: 'enum', + name: 'Fast Scroll Key', + description: 'The modifier key to hold to multiply scroll speed.', + path: ['xterm', 'fastScrollModifier'], + enum: ['none', 'alt', 'shift', 'ctrl'], + }, + { + type: 'number', + name: 'Fast Scroll Multiplier', + description: 'The scroll speed multiplier used for fast scrolling.', + path: ['xterm', 'fastScrollSensitivity'], + float: true, + }, + { + type: 'number', + name: 'Scrollback Rows', + description: + 'The amount of scrollback rows, rows you can scroll up to after they leave the viewport, to keep.', + path: ['xterm', 'scrollback'], + }, + { + type: 'number', + name: 'Tab Stop Width', + description: 'The size of tab stops in the terminal.', + path: ['xterm', 'tabStopWidth'], + }, +]); diff --git a/wetty/src/buffer.ts b/wetty/src/buffer.ts new file mode 100644 index 0000000..bf65832 --- /dev/null +++ b/wetty/src/buffer.ts @@ -0,0 +1,16 @@ +import { createInterface } from 'readline'; + +ask('Enter your username'); + +function ask(question: string): Promise { + const rlp = createInterface({ + input: process.stdin, + output: process.stdout, + }); + return new Promise(resolve => { + rlp.question(`${question}: `, answer => { + rlp.close(); + resolve(answer); + }); + }); +} diff --git a/wetty/src/client/dev.ts b/wetty/src/client/dev.ts new file mode 100644 index 0000000..831a33c --- /dev/null +++ b/wetty/src/client/dev.ts @@ -0,0 +1,5 @@ +caches.keys().then(cacheNames => { + cacheNames.forEach(cacheName => { + caches.delete(cacheName); + }); +}); diff --git a/wetty/src/client/wetty.ts b/wetty/src/client/wetty.ts new file mode 100644 index 0000000..c03f1dd --- /dev/null +++ b/wetty/src/client/wetty.ts @@ -0,0 +1,72 @@ +import { dom, library } from '@fortawesome/fontawesome-svg-core'; +import { faCogs, faKeyboard } from '@fortawesome/free-solid-svg-icons'; +import _ from 'lodash'; + +import '../assets/scss/styles.scss'; + +import { disconnect } from './wetty/disconnect'; +import { overlay } from './wetty/disconnect/elements'; +import { verifyPrompt } from './wetty/disconnect/verify'; +import { FileDownloader } from './wetty/download'; +import { mobileKeyboard } from './wetty/mobile'; +import { socket } from './wetty/socket'; +import { terminal, Term } from './wetty/term'; + +// Setup for fontawesome +library.add(faCogs); +library.add(faKeyboard); +dom.watch(); + +function onResize(term: Term): () => void { + return function resize() { + term.resizeTerm(); + }; +} + +const term = terminal(socket); +if (_.isUndefined(term)) { + disconnect('终端初始化失败:未找到 #terminal 容器'); +} else { + window.addEventListener('beforeunload', verifyPrompt, false); + window.addEventListener('resize', onResize(term), false); + + term.resizeTerm(); + term.focus(); + mobileKeyboard(); + const fileDownloader = new FileDownloader(); + + socket + .on('connect', () => { + if (!_.isNull(overlay)) overlay.style.display = 'none'; + }) + .on('data', (data: string) => { + const remainingData = fileDownloader.buffer(data); + if (remainingData) { + term.write(remainingData); + } + }) + .on('login', () => { + if (!_.isNull(overlay)) overlay.style.display = 'none'; + term.writeln(''); + term.resizeTerm(); + term.focus(); + }) + .on('logout', disconnect) + .on('disconnect', disconnect) + .on('error', (err: string | null) => { + if (err) disconnect(err); + }); + + term.onData((data: string) => { + socket.emit('input', data); + }); + term.onResize((size: { cols: number; rows: number }) => { + socket.emit('resize', size); + }); + + socket.connect().catch((error: unknown) => { + const message = + error instanceof Error ? error.message : '连接网关失败,请检查配置'; + disconnect(message); + }); +} diff --git a/wetty/src/client/wetty/disconnect.ts b/wetty/src/client/wetty/disconnect.ts new file mode 100644 index 0000000..457f209 --- /dev/null +++ b/wetty/src/client/wetty/disconnect.ts @@ -0,0 +1,11 @@ +import _ from 'lodash'; +import { overlay } from './disconnect/elements'; +import { verifyPrompt } from './disconnect/verify'; + +export function disconnect(reason: string): void { + if (_.isNull(overlay)) return; + overlay.style.display = 'block'; + const msg = document.getElementById('msg'); + if (!_.isUndefined(reason) && !_.isNull(msg)) msg.innerHTML = reason; + window.removeEventListener('beforeunload', verifyPrompt, false); +} diff --git a/wetty/src/client/wetty/disconnect/elements.ts b/wetty/src/client/wetty/disconnect/elements.ts new file mode 100644 index 0000000..88c7ec9 --- /dev/null +++ b/wetty/src/client/wetty/disconnect/elements.ts @@ -0,0 +1,5 @@ +export const overlay = document.getElementById('overlay'); +export const terminal = document.getElementById('terminal'); +export const editor = document.querySelector( + '#options .editor', +) as HTMLIFrameElement; diff --git a/wetty/src/client/wetty/disconnect/verify.ts b/wetty/src/client/wetty/disconnect/verify.ts new file mode 100644 index 0000000..97573f1 --- /dev/null +++ b/wetty/src/client/wetty/disconnect/verify.ts @@ -0,0 +1,4 @@ +export function verifyPrompt(e: { returnValue: string }): string { + e.returnValue = 'Are you sure?'; + return e.returnValue; +} diff --git a/wetty/src/client/wetty/download.spec.ts b/wetty/src/client/wetty/download.spec.ts new file mode 100644 index 0000000..57569ef --- /dev/null +++ b/wetty/src/client/wetty/download.spec.ts @@ -0,0 +1,236 @@ +import { expect } from 'chai'; +import 'mocha'; +import { JSDOM } from 'jsdom'; +import * as sinon from 'sinon'; + +import { FileDownloader } from './download'; + +const noop = (): void => {}; // eslint-disable-line @typescript-eslint/no-empty-function + +describe('FileDownloader', () => { + const FILE_BEGIN = 'BEGIN'; + const FILE_END = 'END'; + let fileDownloader: FileDownloader; + + beforeEach(() => { + const { window } = new JSDOM(`...`); + global.document = window.document; + fileDownloader = new FileDownloader(noop, FILE_BEGIN, FILE_END); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should return data before file markers', () => { + const onCompleteFileCallbackStub = sinon.stub( + fileDownloader, + 'onCompleteFileCallback', + ); + expect( + fileDownloader.buffer(`DATA AT THE LEFT${FILE_BEGIN}FILE${FILE_END}`), + ).to.equal('DATA AT THE LEFT'); + expect(onCompleteFileCallbackStub.calledOnce).to.be.true; + expect(onCompleteFileCallbackStub.getCall(0).args[0]).to.equal('FILE'); + }); + + it('should return data after file markers', () => { + const onCompleteFileCallbackStub = sinon.stub( + fileDownloader, + 'onCompleteFileCallback', + ); + expect( + fileDownloader.buffer(`${FILE_BEGIN}FILE${FILE_END}DATA AT THE RIGHT`), + ).to.equal('DATA AT THE RIGHT'); + expect(onCompleteFileCallbackStub.calledOnce).to.be.true; + expect(onCompleteFileCallbackStub.getCall(0).args[0]).to.equal('FILE'); + }); + + it('should return data before and after file markers', () => { + const onCompleteFileCallbackStub = sinon.stub( + fileDownloader, + 'onCompleteFileCallback', + ); + expect( + fileDownloader.buffer( + `DATA AT THE LEFT${FILE_BEGIN}FILE${FILE_END}DATA AT THE RIGHT`, + ), + ).to.equal('DATA AT THE LEFTDATA AT THE RIGHT'); + expect(onCompleteFileCallbackStub.calledOnce).to.be.true; + expect(onCompleteFileCallbackStub.getCall(0).args[0]).to.equal('FILE'); + }); + + it('should return data before a beginning marker found', () => { + sinon.stub(fileDownloader, 'onCompleteFileCallback'); + expect(fileDownloader.buffer(`DATA AT THE LEFT${FILE_BEGIN}FILE`)).to.equal( + 'DATA AT THE LEFT', + ); + }); + + it('should return data after an ending marker found', () => { + const onCompleteFileCallbackStub = sinon.stub( + fileDownloader, + 'onCompleteFileCallback', + ); + expect(fileDownloader.buffer(`${FILE_BEGIN}FI`)).to.equal(''); + expect(fileDownloader.buffer(`LE${FILE_END}DATA AT THE RIGHT`)).to.equal( + 'DATA AT THE RIGHT', + ); + expect(onCompleteFileCallbackStub.calledOnce).to.be.true; + expect(onCompleteFileCallbackStub.getCall(0).args[0]).to.equal('FILE'); + }); + + it('should buffer across incomplete file begin marker sequence on two calls', () => { + fileDownloader = new FileDownloader(noop, 'BEGIN', 'END'); + const onCompleteFileCallbackStub = sinon.stub( + fileDownloader, + 'onCompleteFileCallback', + ); + + expect(fileDownloader.buffer('BEG')).to.equal(''); + expect(fileDownloader.buffer('INFILEEND')).to.equal(''); + expect(onCompleteFileCallbackStub.calledOnce).to.be.true; + expect(onCompleteFileCallbackStub.getCall(0).args[0]).to.equal('FILE'); + }); + + it('should buffer across incomplete file begin marker sequence on n calls', () => { + fileDownloader = new FileDownloader(noop, 'BEGIN', 'END'); + const onCompleteFileCallbackStub = sinon.stub( + fileDownloader, + 'onCompleteFileCallback', + ); + + expect(fileDownloader.buffer('B')).to.equal(''); + expect(fileDownloader.buffer('E')).to.equal(''); + expect(fileDownloader.buffer('G')).to.equal(''); + expect(fileDownloader.buffer('I')).to.equal(''); + expect(fileDownloader.buffer('NFILEEND')).to.equal(''); + expect(onCompleteFileCallbackStub.calledOnce).to.be.true; + expect(onCompleteFileCallbackStub.getCall(0).args[0]).to.equal('FILE'); + }); + + it('should buffer across incomplete file begin marker sequence with data on the left and right on multiple calls', () => { + fileDownloader = new FileDownloader(noop, 'BEGIN', 'END'); + const onCompleteFileCallbackStub = sinon.stub( + fileDownloader, + 'onCompleteFileCallback', + ); + + expect(fileDownloader.buffer('DATA AT THE LEFTB')).to.equal( + 'DATA AT THE LEFT', + ); + expect(fileDownloader.buffer('E')).to.equal(''); + expect(fileDownloader.buffer('G')).to.equal(''); + expect(fileDownloader.buffer('I')).to.equal(''); + expect(fileDownloader.buffer('NFILEENDDATA AT THE RIGHT')).to.equal( + 'DATA AT THE RIGHT', + ); + expect(onCompleteFileCallbackStub.calledOnce).to.be.true; + expect(onCompleteFileCallbackStub.getCall(0).args[0]).to.equal('FILE'); + }); + + it('should buffer across incomplete file begin marker sequence then handle false positive', () => { + fileDownloader = new FileDownloader(noop, 'BEGIN', 'END'); + const onCompleteFileCallbackStub = sinon.stub( + fileDownloader, + 'onCompleteFileCallback', + ); + + expect(fileDownloader.buffer('DATA AT THE LEFTB')).to.equal( + 'DATA AT THE LEFT', + ); + expect(fileDownloader.buffer('E')).to.equal(''); + expect(fileDownloader.buffer('G')).to.equal(''); + // This isn't part of the file_begin marker and should trigger the partial + // file begin marker to be returned with the normal data + expect(fileDownloader.buffer('ZDATA AT THE RIGHT')).to.equal( + 'BEGZDATA AT THE RIGHT', + ); + expect(onCompleteFileCallbackStub.called).to.be.false; + }); + + it('should buffer across incomplete file end marker sequence on two calls', () => { + fileDownloader = new FileDownloader(noop, 'BEGIN', 'END'); + const mockFilePart1 = 'DATA AT THE LEFTBEGINFILEE'; + const mockFilePart2 = 'NDDATA AT THE RIGHT'; + + const onCompleteFileCallbackStub = sinon.stub( + fileDownloader, + 'onCompleteFileCallback', + ); + expect(fileDownloader.buffer(mockFilePart1)).to.equal('DATA AT THE LEFT'); + expect(fileDownloader.buffer(mockFilePart2)).to.equal('DATA AT THE RIGHT'); + + expect(onCompleteFileCallbackStub.calledOnce).to.be.true; + expect(onCompleteFileCallbackStub.getCall(0).args[0]).to.equal('FILE'); + }); + + it('should buffer across incomplete file end and file begin marker sequence with data on the left and right on multiple calls', () => { + fileDownloader = new FileDownloader(noop, 'BEGIN', 'END'); + const onCompleteFileCallbackStub = sinon.stub( + fileDownloader, + 'onCompleteFileCallback', + ); + + expect(fileDownloader.buffer('DATA AT THE LEFTBE')).to.equal( + 'DATA AT THE LEFT', + ); + expect(fileDownloader.buffer('G')).to.equal(''); + expect(fileDownloader.buffer('I')).to.equal(''); + expect(fileDownloader.buffer('NFILEE')).to.equal(''); + expect(fileDownloader.buffer('N')).to.equal(''); + expect(fileDownloader.buffer('DDATA AT THE RIGHT')).to.equal( + 'DATA AT THE RIGHT', + ); + expect(onCompleteFileCallbackStub.calledOnce).to.be.true; + expect(onCompleteFileCallbackStub.getCall(0).args[0]).to.equal('FILE'); + }); + + it('should be able to handle multiple files', () => { + fileDownloader = new FileDownloader(noop, 'BEGIN', 'END'); + const onCompleteFileCallbackStub = sinon.stub( + fileDownloader, + 'onCompleteFileCallback', + ); + + expect( + fileDownloader.buffer( + 'DATA AT THE LEFT' + + 'BEGIN' + + 'FILE1' + + 'END' + + 'SECOND DATA' + + 'BEGIN', + ), + ).to.equal('DATA AT THE LEFTSECOND DATA'); + expect(onCompleteFileCallbackStub.calledOnce).to.be.true; + expect(onCompleteFileCallbackStub.getCall(0).args[0]).to.equal('FILE1'); + + expect(fileDownloader.buffer('FILE2')).to.equal(''); + expect(fileDownloader.buffer('E')).to.equal(''); + expect(fileDownloader.buffer('NDRIGHT')).to.equal('RIGHT'); + expect(onCompleteFileCallbackStub.calledTwice).to.be.true; + expect(onCompleteFileCallbackStub.getCall(1).args[0]).to.equal('FILE2'); + }); + + it('should be able to handle multiple files with an ending marker', () => { + fileDownloader = new FileDownloader(noop, 'BEGIN', 'END'); + const onCompleteFileCallbackStub = sinon.stub( + fileDownloader, + 'onCompleteFileCallback', + ); + + expect(fileDownloader.buffer('DATA AT THE LEFTBEGINFILE1EN')).to.equal( + 'DATA AT THE LEFT', + ); + expect(onCompleteFileCallbackStub.calledOnce).to.be.false; + expect(fileDownloader.buffer('DSECOND DATABEGINFILE2EN')).to.equal( + 'SECOND DATA', + ); + expect(onCompleteFileCallbackStub.calledOnce).to.be.true; + expect(onCompleteFileCallbackStub.getCall(0).args[0]).to.equal('FILE1'); + expect(fileDownloader.buffer('D')).to.equal(''); + expect(onCompleteFileCallbackStub.calledTwice).to.be.true; + expect(onCompleteFileCallbackStub.getCall(1).args[0]).to.equal('FILE2'); + }); +}); diff --git a/wetty/src/client/wetty/download.ts b/wetty/src/client/wetty/download.ts new file mode 100644 index 0000000..8ee96e0 --- /dev/null +++ b/wetty/src/client/wetty/download.ts @@ -0,0 +1,169 @@ +import fileType from 'file-type'; +import Toastify from 'toastify-js'; + +const DEFAULT_FILE_BEGIN = '\u001b[5i'; +const DEFAULT_FILE_END = '\u001b[4i'; + +type OnCompleteFile = (bufferCharacters: string) => void; + +function onCompleteFile(bufferCharacters: string): void { + let fileNameBase64; + let fileCharacters = bufferCharacters; + if (bufferCharacters.includes(":")) { + [fileNameBase64, fileCharacters] = bufferCharacters.split(":"); + } + // Try to decode it as base64, if it fails we assume it's not base64 + try { + fileCharacters = window.atob(fileCharacters); + } catch (err) { + // Assuming it's not base64... + } + + const bytes = new Uint8Array(fileCharacters.length); + for (let i = 0; i < fileCharacters.length; i += 1) { + bytes[i] = fileCharacters.charCodeAt(i); + } + + let mimeType = 'application/octet-stream'; + let fileExt = ''; + const typeData = fileType(bytes); + if (typeData) { + mimeType = typeData.mime; + fileExt = typeData.ext; + } + // Check if the buffer is ASCII + // Ref: https://stackoverflow.com/a/14313213 + // eslint-disable-next-line no-control-regex + else if (/^[\x00-\xFF]*$/.test(fileCharacters)) { + mimeType = 'text/plain'; + fileExt = 'txt'; + } + let fileName; + try { + if (fileNameBase64 !== undefined) { + fileName = window.atob(fileNameBase64); + } + } catch (err) { + // Filename wasn't base64-encoded so let's ignore it + } + + if (fileName === undefined) { + fileName = `file-${new Date() + .toISOString() + .split('.')[0] + .replace(/-/g, '') + .replace('T', '') + .replace(/:/g, '')}${fileExt ? `.${fileExt}` : ''}`; + } + + const blob = new Blob([new Uint8Array(bytes.buffer)], { + type: mimeType, + }); + const blobUrl = URL.createObjectURL(blob); + + Toastify({ + text: `Download ready: ${fileName}`, + duration: 10000, + newWindow: true, + gravity: 'bottom', + position: 'right', + backgroundColor: '#fff', + stopOnFocus: true, + escapeMarkup: false, + }).showToast(); +} + +export class FileDownloader { + fileBuffer: string[]; + fileBegin: string; + fileEnd: string; + partialFileBegin: string; + onCompleteFileCallback: OnCompleteFile; + + constructor( + onCompleteFileCallback: OnCompleteFile = onCompleteFile, + fileBegin: string = DEFAULT_FILE_BEGIN, + fileEnd: string = DEFAULT_FILE_END, + ) { + this.fileBuffer = []; + this.fileBegin = fileBegin; + this.fileEnd = fileEnd; + this.partialFileBegin = ''; + this.onCompleteFileCallback = onCompleteFileCallback; + } + + bufferCharacter(character: string): string { + // If we are not currently buffering a file. + if (this.fileBuffer.length === 0) { + // If we are not currently expecting the rest of the fileBegin sequences. + if (this.partialFileBegin.length === 0) { + // If the character is the first character of fileBegin we know to start + // expecting the rest of the fileBegin sequence. + if (character === this.fileBegin[0]) { + this.partialFileBegin = character; + return ''; + } + // Otherwise, we just return the character for printing to the terminal. + + return character; + } + // We're currently in the state of buffering a beginner marker... + + const nextExpectedCharacter = + this.fileBegin[this.partialFileBegin.length]; + // If the next character *is* the next character in the fileBegin sequence. + if (character === nextExpectedCharacter) { + this.partialFileBegin += character; + // Do we now have the complete fileBegin sequence. + if (this.partialFileBegin === this.fileBegin) { + this.partialFileBegin = ''; + this.fileBuffer = this.fileBuffer.concat(this.fileBegin.split('')); + return ''; + } + // Otherwise, we just wait until the next character. + + return ''; + } + // If the next expected character wasn't found for the fileBegin sequence, + // we need to return all the data that was bufferd in the partialFileBegin + // back to the terminal. + + const dataToReturn = this.partialFileBegin + character; + this.partialFileBegin = ''; + return dataToReturn; + } + // If we are currently in the state of buffering a file. + + this.fileBuffer.push(character); + // If we now have an entire fileEnd marker, we have a complete file! + if ( + this.fileBuffer.length >= this.fileBegin.length + this.fileEnd.length && + this.fileBuffer.slice(-this.fileEnd.length).join('') === this.fileEnd + ) { + this.onCompleteFileCallback( + this.fileBuffer + .slice( + this.fileBegin.length, + this.fileBuffer.length - this.fileEnd.length, + ) + .join(''), + ); + this.fileBuffer = []; + } + + return ''; + } + + buffer(data: string): string { + // This is a optimization to quickly return if we know for + // sure we don't need to loop over each character. + if ( + this.fileBuffer.length === 0 && + this.partialFileBegin.length === 0 && + data.indexOf(this.fileBegin[0]) === -1 + ) { + return data; + } + return data.split('').map(this.bufferCharacter.bind(this)).join(''); + } +} diff --git a/wetty/src/client/wetty/flowcontrol.ts b/wetty/src/client/wetty/flowcontrol.ts new file mode 100644 index 0000000..c0c1798 --- /dev/null +++ b/wetty/src/client/wetty/flowcontrol.ts @@ -0,0 +1,24 @@ +/** + * Flow control client side. + * For low impact on overall throughput simply commits every `ackBytes` + * (default 2^18). + */ +export class FlowControlClient { + public counter = 0; + public ackBytes = 262144; + + constructor(ackBytes?: number) { + if (ackBytes) { + this.ackBytes = ackBytes; + } + } + + public needsCommit(length: number): boolean { + this.counter += length; + if (this.counter >= this.ackBytes) { + this.counter -= this.ackBytes; + return true; + } + return false; + } +} diff --git a/wetty/src/client/wetty/mobile.ts b/wetty/src/client/wetty/mobile.ts new file mode 100644 index 0000000..936f70c --- /dev/null +++ b/wetty/src/client/wetty/mobile.ts @@ -0,0 +1,14 @@ +import _ from 'lodash'; + +export function mobileKeyboard(): void { + const [screen] = Array.from(document.getElementsByClassName('xterm-screen')); + if (_.isNull(screen)) return; + screen.setAttribute('contenteditable', 'true'); + screen.setAttribute('spellcheck', 'false'); + screen.setAttribute('autocorrect', 'false'); + screen.setAttribute('autocomplete', 'false'); + screen.setAttribute('autocapitalize', 'false'); + /* + term.scrollPort_.screen_.setAttribute('contenteditable', 'false'); + */ +} diff --git a/wetty/src/client/wetty/runtimeConfig.ts b/wetty/src/client/wetty/runtimeConfig.ts new file mode 100644 index 0000000..1b9e864 --- /dev/null +++ b/wetty/src/client/wetty/runtimeConfig.ts @@ -0,0 +1,118 @@ +export type GatewayAuthType = 'password' | 'privateKey' | 'certificate'; + +export interface TerminalServerConfig { + id: string; + name?: string; + host: string; + port: number; + username: string; + transportMode?: string; + authType: GatewayAuthType; + password?: string; + privateKey?: string; + passphrase?: string; + certificate?: string; + knownHostFingerprint?: string; + cols?: number; + rows?: number; +} + +export interface TerminalRuntimeConfig { + gatewayUrl?: string; + gatewayToken?: string; + selectedServerId?: string; + servers: TerminalServerConfig[]; +} + +function isObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +function asString(value: unknown): string | undefined { + return typeof value === 'string' ? value : undefined; +} + +function asNumber(value: unknown): number | undefined { + return typeof value === 'number' ? value : undefined; +} + +function parseServer(input: unknown): TerminalServerConfig | undefined { + if (!isObject(input)) return undefined; + + const id = asString(input.id); + const host = asString(input.host); + const username = asString(input.username); + const authType = asString(input.authType) as GatewayAuthType | undefined; + const port = asNumber(input.port); + + if (!id || !host || !username || !authType || !port) return undefined; + if (!['password', 'privateKey', 'certificate'].includes(authType)) { + return undefined; + } + + return { + id, + host, + port, + username, + authType, + name: asString(input.name), + transportMode: asString(input.transportMode), + password: asString(input.password), + privateKey: asString(input.privateKey), + passphrase: asString(input.passphrase), + certificate: asString(input.certificate), + knownHostFingerprint: asString(input.knownHostFingerprint), + cols: asNumber(input.cols), + rows: asNumber(input.rows), + }; +} + +/** + * 加载终端运行配置: + * 1) 统一从项目根目录 `terminal.config.json` 读取; + * 2) 使用 `no-store` 避免开发阶段被浏览器缓存; + * 3) 只返回通过最小字段校验的服务器条目,减少运行时协议错误。 + */ +export async function loadRuntimeConfig(): Promise { + const response = await fetch('/terminal.config.json', { + cache: 'no-store', + }); + if (!response.ok) { + throw new Error(`读取 terminal.config.json 失败: HTTP ${response.status}`); + } + + const data = (await response.json()) as unknown; + if (!isObject(data)) { + throw new Error('terminal.config.json 格式错误: 顶层必须是对象'); + } + + const serversInput = Array.isArray(data.servers) ? data.servers : []; + const servers = serversInput + .map(parseServer) + .filter((item): item is TerminalServerConfig => Boolean(item)); + + if (servers.length === 0) { + throw new Error('terminal.config.json 格式错误: servers 不能为空'); + } + + return { + gatewayUrl: asString(data.gatewayUrl), + gatewayToken: asString(data.gatewayToken), + selectedServerId: asString(data.selectedServerId), + servers, + }; +} + +export function selectServer( + config: TerminalRuntimeConfig, +): TerminalServerConfig { + if (config.selectedServerId) { + const selected = config.servers.find( + (server) => server.id === config.selectedServerId, + ); + if (selected) return selected; + } + + return config.servers[0]; +} diff --git a/wetty/src/client/wetty/socket.ts b/wetty/src/client/wetty/socket.ts new file mode 100644 index 0000000..afda6b1 --- /dev/null +++ b/wetty/src/client/wetty/socket.ts @@ -0,0 +1,273 @@ +import { loadRuntimeConfig, selectServer } from './runtimeConfig'; + +type SocketEvent = + | 'connect' + | 'login' + | 'data' + | 'logout' + | 'disconnect' + | 'error'; + +type EventPayloadMap = { + connect: []; + login: []; + data: [string]; + logout: [string]; + disconnect: [string]; + error: [string]; +}; + +type EventHandler = (...args: EventPayloadMap[K]) => void; + +type EmitEvent = 'input' | 'resize' | 'commit'; + +interface ResizePayload { + cols: number; + rows: number; +} + +interface GatewayFrame { + type: string; + payload?: Record; +} + +interface GatewaySocketOptions { + cols: number; + rows: number; +} + +export interface TermSocketLike { + emit(event: EmitEvent, payload: string | number | ResizePayload): void; +} + +export class GatewaySocket { + private ws: WebSocket | null = null; + private options: GatewaySocketOptions = { cols: 80, rows: 24 }; + private initialized = false; + private handlers = new Map void>>(); + + public on(event: K, handler: EventHandler): this { + if (!this.handlers.has(event)) { + this.handlers.set(event, new Set<(...args: unknown[]) => void>()); + } + this.handlers.get(event)?.add(handler as (...args: unknown[]) => void); + return this; + } + + public emit( + event: EmitEvent, + payload: string | number | ResizePayload, + ): void { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return; + if (!this.initialized && event !== 'resize') return; + + if (event === 'input' && typeof payload === 'string') { + this.sendFrame({ + type: 'stdin', + payload: { data: payload, meta: { source: 'keyboard' } }, + }); + return; + } + + if ( + event === 'resize' && + typeof payload === 'object' && + payload !== null && + typeof (payload as ResizePayload).cols === 'number' && + typeof (payload as ResizePayload).rows === 'number' + ) { + const size = payload as ResizePayload; + this.options = { cols: size.cols, rows: size.rows }; + this.sendFrame({ + type: 'resize', + payload: size as unknown as Record, + }); + } + } + + public async connect(): Promise { + try { + const runtime = await loadRuntimeConfig(); + const selectedServer = selectServer(runtime); + this.options = { + cols: selectedServer.cols ?? 80, + rows: selectedServer.rows ?? 24, + }; + + const wsUrl = GatewaySocket.buildGatewayWsUrl( + runtime.gatewayUrl, + runtime.gatewayToken, + ); + this.ws = new WebSocket(wsUrl); + + this.ws.addEventListener('open', () => { + this.fire('connect'); + this.sendFrame({ + type: 'init', + payload: this.buildInitPayload(selectedServer), + }); + }); + + this.ws.addEventListener('message', (event) => { + this.handleMessage(event.data); + }); + + this.ws.addEventListener('close', (event) => { + const reason = event.reason || `连接已关闭 (code=${event.code})`; + if (this.initialized) { + this.fire('logout', reason); + } + this.fire('disconnect', reason); + this.initialized = false; + this.ws = null; + }); + + this.ws.addEventListener('error', () => { + this.fire('error', '网关连接异常'); + }); + } catch (error) { + this.fire( + 'error', + error instanceof Error ? error.message : '初始化网关连接失败', + ); + } + } + + private static buildGatewayWsUrl( + rawUrl: string | undefined, + token?: string, + ): string { + const defaultProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const defaultUrl = `${defaultProtocol}//${window.location.hostname}:8787`; + const normalized = rawUrl && rawUrl.trim() ? rawUrl : defaultUrl; + + const url = new URL(normalized); + if (url.protocol !== 'ws:' && url.protocol !== 'wss:') { + url.protocol = defaultProtocol; + } + url.pathname = '/ws/terminal'; + if (token) { + url.searchParams.set('token', token); + } + return url.toString(); + } + + /** + * 构建 gateway init 帧: + * 1) 认证信息按 authType 映射到协议要求字段; + * 2) `clientSessionKey` 固定化,支持网关侧短时断线续接; + * 3) 初始窗口大小从配置注入,后续再由 xterm resize 覆盖。 + */ + private buildInitPayload( + server: Awaited>, + ): Record { + const clientSessionKey = `${server.id}:${server.username}@${server.host}:${server.port}`; + const payload: Record = { + host: server.host, + port: server.port, + username: server.username, + clientSessionKey, + pty: { + cols: this.options.cols, + rows: this.options.rows, + }, + }; + + if (server.knownHostFingerprint) { + payload.knownHostFingerprint = server.knownHostFingerprint; + } + + if (server.authType === 'password') { + payload.credential = { + type: 'password', + password: server.password || '', + }; + return payload; + } + + if (server.authType === 'privateKey') { + payload.credential = { + type: 'privateKey', + privateKey: server.privateKey || '', + ...server.passphrase ? { passphrase: server.passphrase } : {}, + }; + return payload; + } + + payload.credential = { + type: 'certificate', + privateKey: server.privateKey || '', + certificate: server.certificate || '', + ...server.passphrase ? { passphrase: server.passphrase } : {}, + }; + return payload; + } + + private handleMessage(raw: string | Blob | ArrayBuffer): void { + let data = ''; + + if (typeof raw === 'string') { + data = raw; + } else if (raw instanceof ArrayBuffer) { + data = new TextDecoder().decode(raw); + } else { + return; + } + + let frame: GatewayFrame; + try { + frame = JSON.parse(data) as GatewayFrame; + } catch { + return; + } + + if (!frame || typeof frame.type !== 'string') return; + + if (frame.type === 'stdout' || frame.type === 'stderr') { + const text = `${frame.payload?.data || ''}`; + if (text) this.fire('data', text); + return; + } + + if (frame.type === 'error') { + const msg = `${frame.payload?.message || '网关返回错误'}`; + this.fire('error', msg); + return; + } + + if (frame.type === 'control') { + const action = `${frame.payload?.action || ''}`; + if (action === 'ping') { + this.sendFrame({ type: 'control', payload: { action: 'pong' } }); + return; + } + if (action === 'connected') { + if (!this.initialized) { + this.initialized = true; + this.fire('login'); + } + return; + } + if (action === 'disconnect') { + const reason = `${frame.payload?.reason || '连接已断开'}`; + this.fire('logout', reason); + } + } + } + + private sendFrame(frame: GatewayFrame): void { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return; + this.ws.send(JSON.stringify(frame)); + } + + private fire( + event: K, + ...args: EventPayloadMap[K] + ): void { + this.handlers.get(event)?.forEach((handler) => { + handler(...args); + }); + } +} + +export const socket = new GatewaySocket(); diff --git a/wetty/src/client/wetty/term.ts b/wetty/src/client/wetty/term.ts new file mode 100644 index 0000000..1c2963b --- /dev/null +++ b/wetty/src/client/wetty/term.ts @@ -0,0 +1,236 @@ +import { FitAddon } from '@xterm/addon-fit'; +import { ImageAddon } from '@xterm/addon-image'; +import { WebLinksAddon } from '@xterm/addon-web-links'; +import { Terminal } from '@xterm/xterm'; +import _ from 'lodash'; + +import { terminal as termElement } from './disconnect/elements'; +import { configureTerm } from './term/confiruragtion'; +import { loadOptions } from './term/load'; +import type { TermSocketLike } from './socket'; +import type { Options } from './term/options'; + +export class Term extends Terminal { + socket: TermSocketLike; + fitAddon: FitAddon; + loadOptions: () => Options; + + constructor(socket: TermSocketLike) { + super({ allowProposedApi: true }); + this.socket = socket; + this.fitAddon = new FitAddon(); + this.loadAddon(this.fitAddon); + this.loadAddon(new WebLinksAddon()); + this.loadAddon(new ImageAddon()); + this.loadOptions = loadOptions; + } + + resizeTerm(): void { + this.refresh(0, this.rows - 1); + if (this.shouldFitTerm) this.fitAddon.fit(); + this.socket.emit('resize', { cols: this.cols, rows: this.rows }); + } + + get shouldFitTerm(): boolean { + return this.loadOptions().wettyFitTerminal ?? true; + } +} + +const ctrlButton = document.getElementById('onscreen-ctrl'); +let ctrlFlag = false; // This indicates whether the CTRL key is pressed or not + +/** + * Toggles the state of the `ctrlFlag` variable and updates the visual state + * of the `ctrlButton` element accordingly. If `ctrlFlag` is set to `true`, + * the `active` class is added to the `ctrlButton`; otherwise, it is removed. + * After toggling, the terminal (`wetty_term`) is focused if it exists. + */ +const toggleCTRL = (): void => { + ctrlFlag = !ctrlFlag; + if (ctrlButton) { + if (ctrlFlag) { + ctrlButton.classList.add('active'); + } else { + ctrlButton.classList.remove('active'); + } + } + window.wetty_term?.focus(); +} + +/** + * Simulates a backspace key press by sending the backspace character + * (ASCII code 127) to the terminal. This function is intended to be used + * in conjunction with the `simulateCTRLAndKey` function to handle + * keyboard shortcuts. + * + */ +const simulateBackspace = (): void => { + window.wetty_term?.input('\x7F', true); +} + +/** + * Simulates a CTRL + key press by sending the corresponding character + * (converted from the key's ASCII code) to the terminal. This function + * is intended to be used in conjunction with the `toggleCTRL` function + * to handle keyboard shortcuts. + * + * @param key - The key that was pressed, which will be converted to + * its corresponding character code. + */ +const simulateCTRLAndKey = (key: string): void => { + window.wetty_term?.input(`${String.fromCharCode(key.toUpperCase().charCodeAt(0) - 64)}`, false); +} + +/** + * Handles the keydown event for the CTRL key. When the CTRL key is pressed, + * it sets the `ctrlFlag` variable to true and updates the visual state of + * the `ctrlButton` element. If the CTRL key is released, it sets `ctrlFlag` + * to false and updates the visual state of the `ctrlButton` element. + * + * @param e - The keyboard event object. + */ +document.addEventListener('keyup', (e) => { + if (ctrlFlag) { + // if key is a character + if (e.key.length === 1 && e.key.match(/^[a-zA-Z0-9]$/)) { + simulateCTRLAndKey(e.key); + // delayed backspace is needed to remove the character added to the terminal + // when CTRL + key is pressed. + // this is a workaround because e.preventDefault() cannot be used. + _.debounce(() => { + simulateBackspace(); + }, 100)(); + } + toggleCTRL(); + } +}); + +/** + * Simulates pressing the ESC key by sending the ESC character (ASCII code 27) + * to the terminal. If the CTRL key is active, it toggles the CTRL state off. + * After sending the ESC character, the terminal is focused. + */ +const pressESC = (): void => { + if (ctrlFlag) { + toggleCTRL(); + } + window.wetty_term?.input('\x1B', false); + window.wetty_term?.focus(); +} + +/** + * Simulates pressing the UP arrow key by sending the UP character (ASCII code 65) + * to the terminal. If the CTRL key is active, it toggles the CTRL state off. + * After sending the UP character, the terminal is focused. + */ +const pressUP = (): void => { + if (ctrlFlag) { + toggleCTRL(); + } + window.wetty_term?.input('\x1B[A', false); + window.wetty_term?.focus(); +} + +/** + * Simulates pressing the DOWN arrow key by sending the DOWN character (ASCII code 66) + * to the terminal. If the CTRL key is active, it toggles the CTRL state off. + * After sending the DOWN character, the terminal is focused. + */ +const pressDOWN = (): void => { + if (ctrlFlag) { + toggleCTRL(); + } + window.wetty_term?.input('\x1B[B', false); + window.wetty_term?.focus(); +} + +/** + * Simulates pressing the TAB key by sending the TAB character (ASCII code 9) + * to the terminal. If the CTRL key is active, it toggles the CTRL state off. + * After sending the TAB character, the terminal is focused. + */ +const pressTAB = (): void => { + if (ctrlFlag) { + toggleCTRL(); + } + window.wetty_term?.input('\x09', false); + window.wetty_term?.focus(); +} + +/** + * Simulates pressing the LEFT arrow key by sending the LEFT character (ASCII code 68) + * to the terminal. If the CTRL key is active, it toggles the CTRL state off. + * After sending the LEFT character, the terminal is focused. + */ +const pressLEFT = (): void => { + if (ctrlFlag) { + toggleCTRL(); + } + window.wetty_term?.input('\x1B[D', false); + window.wetty_term?.focus(); +} + +/** + * Simulates pressing the RIGHT arrow key by sending the RIGHT character (ASCII code 67) + * to the terminal. If the CTRL key is active, it toggles the CTRL state off. + * After sending the RIGHT character, the terminal is focused. + */ +const pressRIGHT = (): void => { + if (ctrlFlag) { + toggleCTRL(); + } + window.wetty_term?.input('\x1B[C', false); + window.wetty_term?.focus(); +} + +/** + * Toggles the visibility of the onscreen buttons by adding or removing + * the 'active' class to the element with the ID 'onscreen-buttons'. + */ +const toggleFunctions = (): void => { + const element = document.querySelector('div#functions > div.onscreen-buttons') + if (element?.classList.contains('active')) { + element?.classList.remove('active'); + } else { + element?.classList.add('active'); + } +} + +declare global { + interface Window { + wetty_term?: Term; + wetty_close_config?: () => void; + wetty_save_config?: (newConfig: Options) => void; + clipboardData: DataTransfer; + loadOptions: (conf: Options) => void; + toggleFunctions?: () => void; + toggleCTRL? : () => void; + pressESC?: () => void; + pressUP?: () => void; + pressDOWN?: () => void; + pressTAB?: () => void; + pressLEFT?: () => void; + pressRIGHT?: () => void; + } +} + +export function terminal(socket: TermSocketLike): Term | undefined { + const term = new Term(socket); + if (_.isNull(termElement)) return undefined; + termElement.innerHTML = ''; + term.open(termElement); + configureTerm(term); + window.onresize = function onResize() { + term.resizeTerm(); + }; + window.wetty_term = term; + window.toggleFunctions = toggleFunctions; + window.toggleCTRL = toggleCTRL; + window.pressESC = pressESC; + window.pressUP = pressUP; + window.pressDOWN = pressDOWN; + window.pressTAB = pressTAB; + window.pressLEFT = pressLEFT; + window.pressRIGHT = pressRIGHT; + return term; +} diff --git a/wetty/src/client/wetty/term/confiruragtion.ts b/wetty/src/client/wetty/term/confiruragtion.ts new file mode 100644 index 0000000..9c81150 --- /dev/null +++ b/wetty/src/client/wetty/term/confiruragtion.ts @@ -0,0 +1,77 @@ +import { editor } from '../disconnect/elements'; +import { copySelected, copyShortcut } from './confiruragtion/clipboard'; +import { onInput } from './confiruragtion/editor'; +import { loadOptions } from './load'; +import type { Options } from './options'; +import type { Term } from '../term'; + +export function configureTerm(term: Term): void { + const options = loadOptions(); + try { + term.options = options.xterm; + } catch { + /* Do nothing */ + } + + const toggle = document.querySelector('#options .toggler'); + const optionsElem = document.getElementById('options'); + if (editor == null || toggle == null || optionsElem == null) { + throw new Error("Couldn't initialize configuration menu"); + } + + /** + * 将当前配置同步给 iframe 配置面板,并注入回调钩子。 + * 返回值表示 iframe 是否已准备好接收配置(loadOptions 已可调用)。 + */ + const syncEditor = (): boolean => { + const editorWindow = editor.contentWindow; + if (!editorWindow || typeof editorWindow.loadOptions !== 'function') { + return false; + } + + editorWindow.loadOptions(loadOptions()); + editorWindow.wetty_close_config = () => { + optionsElem?.classList.toggle('opened'); + }; + editorWindow.wetty_save_config = (newConfig: Options) => { + onInput(term, newConfig); + }; + return true; + }; + + function editorOnLoad() { + if (syncEditor()) return; + + // 某些浏览器/开发模式下,iframe 的脚本初始化会略晚于 load 事件。 + setTimeout(() => { + syncEditor(); + }, 50); + } + if ( + ( + editor.contentDocument || + (editor.contentWindow?.document ?? { + readyState: '', + }) + ).readyState === 'complete' + ) { + editorOnLoad(); + } + editor.addEventListener('load', editorOnLoad); + + toggle.addEventListener('click', e => { + syncEditor(); + optionsElem.classList.toggle('opened'); + e.preventDefault(); + }); + + term.attachCustomKeyEventHandler(copyShortcut); + + document.addEventListener( + 'mouseup', + () => { + if (term.hasSelection()) copySelected(term.getSelection()); + }, + false, + ); +} diff --git a/wetty/src/client/wetty/term/confiruragtion/clipboard.ts b/wetty/src/client/wetty/term/confiruragtion/clipboard.ts new file mode 100644 index 0000000..33cde21 --- /dev/null +++ b/wetty/src/client/wetty/term/confiruragtion/clipboard.ts @@ -0,0 +1,40 @@ +/** + Copy text selection to clipboard on double click or select + @param text - the selected text to copy + @returns boolean to indicate success or failure + */ +export function copySelected(text: string): boolean { + if (window.clipboardData?.setData) { + window.clipboardData.setData('Text', text); + return true; + } + if ( + document.queryCommandSupported && + document.queryCommandSupported('copy') + ) { + const textarea = document.createElement('textarea'); + textarea.textContent = text; + textarea.style.position = 'fixed'; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + return true; + } catch (ex) { + return false; + } finally { + document.body.removeChild(textarea); + } + } + return false; +} + +export function copyShortcut(e: KeyboardEvent): boolean { + // Ctrl + Shift + C + if (e.ctrlKey && e.shiftKey && e.keyCode === 67) { + e.preventDefault(); + document.execCommand('copy'); + return false; + } + return true; +} diff --git a/wetty/src/client/wetty/term/confiruragtion/editor.ts b/wetty/src/client/wetty/term/confiruragtion/editor.ts new file mode 100644 index 0000000..6ebc2f8 --- /dev/null +++ b/wetty/src/client/wetty/term/confiruragtion/editor.ts @@ -0,0 +1,23 @@ +import { editor } from '../../disconnect/elements'; +import type { Term } from '../../term'; +import type { Options } from '../options'; + +export const onInput = (term: Term, updated: Options) => { + try { + const updatedConf = JSON.stringify(updated, null, 2); + if (localStorage.options === updatedConf) return; + term.options = updated.xterm; + if ( + !updated.wettyFitTerminal && + updated.xterm.cols != null && + updated.xterm.rows != null + ) + term.resize(updated.xterm.cols, updated.xterm.rows); + term.resizeTerm(); + editor.classList.remove('error'); + localStorage.options = updatedConf; + } catch (e) { + console.error('Configuration Error', e); // eslint-disable-line no-console + editor.classList.add('error'); + } +}; diff --git a/wetty/src/client/wetty/term/load.ts b/wetty/src/client/wetty/term/load.ts new file mode 100644 index 0000000..2a50181 --- /dev/null +++ b/wetty/src/client/wetty/term/load.ts @@ -0,0 +1,25 @@ +import _ from 'lodash'; +import type { XTerm, Options } from './options'; + +export const defaultOptions: Options = { + xterm: { fontSize: 14 }, + wettyVoid: 0, + wettyFitTerminal: true, +}; + +export function loadOptions(): Options { + try { + let options = _.isUndefined(localStorage.options) + ? defaultOptions + : JSON.parse(localStorage.options); + // Convert old options to new options + if (!('xterm' in options)) { + const xterm = options; + options = defaultOptions; + options.xterm = xterm as unknown as XTerm; + } + return options; + } catch { + return defaultOptions; + } +} diff --git a/wetty/src/client/wetty/term/options.ts b/wetty/src/client/wetty/term/options.ts new file mode 100644 index 0000000..aaa54ed --- /dev/null +++ b/wetty/src/client/wetty/term/options.ts @@ -0,0 +1,11 @@ +export type XTerm = { + cols?: number; + rows?: number; + fontSize: number; +} & Record; + +export interface Options { + xterm: XTerm; + wettyFitTerminal: boolean; + wettyVoid: number; +} diff --git a/wetty/src/main.ts b/wetty/src/main.ts new file mode 100644 index 0000000..6a3b4c4 --- /dev/null +++ b/wetty/src/main.ts @@ -0,0 +1,160 @@ +#!/usr/bin/env node + +/** + * Create WeTTY server + * @module WeTTy + * + * This is the cli Interface for wetty. + */ +import { unlinkSync, existsSync, lstatSync } from 'fs'; +import { createRequire } from 'module'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import { start } from './server.js'; +import { loadConfigFile, mergeCliConf } from './shared/config.js'; +import { setLevel, logger } from './shared/logger.js'; + +/* eslint-disable @typescript-eslint/no-var-requires */ +const require = createRequire(import.meta.url); +const packageJson = require('../package.json'); + +const opts = yargs(hideBin(process.argv)) + .scriptName(packageJson.name) + .version(packageJson.version) + .options('conf', { + type: 'string', + description: 'config file to load config from', + }) + .option('ssl-key', { + type: 'string', + description: 'path to SSL key', + }) + .option('ssl-cert', { + type: 'string', + description: 'path to SSL certificate', + }) + .option('ssh-host', { + description: 'ssh server host', + type: 'string', + }) + .option('ssh-port', { + description: 'ssh server port', + type: 'number', + }) + .option('ssh-user', { + description: 'ssh user', + type: 'string', + }) + .option('title', { + description: 'window title', + type: 'string', + }) + .option('ssh-auth', { + description: + 'defaults to "password", you can use "publickey,password" instead', + type: 'string', + }) + .option('ssh-pass', { + description: 'ssh password', + type: 'string', + }) + .option('ssh-key', { + demand: false, + description: + 'path to an optional client private key (connection will be password-less and insecure!)', + type: 'string', + }) + .option('ssh-config', { + description: + 'Specifies an alternative ssh configuration file. For further details see "-F" option in ssh(1)', + type: 'string', + }) + .option('force-ssh', { + description: 'Connecting through ssh even if running as root', + type: 'boolean', + }) + .option('known-hosts', { + description: 'path to known hosts file', + type: 'string', + }) + .option('base', { + alias: 'b', + description: 'base path to wetty', + type: 'string', + }) + .option('port', { + alias: 'p', + description: 'wetty listen port', + type: 'number', + }) + .option('host', { + description: 'wetty listen host', + type: 'string', + }) + .option('socket', { + description: 'Make wetty listen on unix socket', + type: 'string', + }) + .option('command', { + alias: 'c', + description: 'command to run in shell', + type: 'string', + }) + .option('allow-iframe', { + description: + 'Allow WeTTY to be embedded in an iframe, defaults to allowing same origin', + type: 'boolean', + }) + .option('allow-remote-hosts', { + description: + 'Allow WeTTY to use the `host` and `port` params in a url as ssh destination', + type: 'boolean', + }) + .option('allow-remote-command', { + description: + 'Allow WeTTY to use the `command` and `path` params in a url as command and working directory on ssh host', + type: 'boolean', + }) + .option('log-level', { + description: 'set log level of wetty server', + type: 'string', + }) + .option('help', { + alias: 'h', + type: 'boolean', + description: 'Print help message', + }) + .conflicts('host', 'socket') + .conflicts('port', 'socket') + .boolean('allow_discovery') + .parseSync(); + +function cleanup() { + if (opts.socket) { + const socket = opts.socket.toString(); + if (existsSync(socket) && lstatSync(socket).isSocket()) { + unlinkSync(socket); + } + } +} +function exit() { + process.exit(1); +} + +if (!opts.help) { + process.on('SIGINT', exit); + process.on('exit', cleanup); + loadConfigFile(opts.conf) + .then((config) => mergeCliConf(opts, config)) + .then((conf) => { + setLevel(conf.logLevel); + start(conf.ssh, conf.server, conf.command, conf.forceSSH, conf.ssl); + }) + .catch((err: Error) => { + logger().error('error in server', { err }); + process.exitCode = 1; + }); +} else { + yargs.showHelp(); + process.exitCode = 0; +} diff --git a/wetty/src/server.ts b/wetty/src/server.ts new file mode 100644 index 0000000..d061fde --- /dev/null +++ b/wetty/src/server.ts @@ -0,0 +1,88 @@ +/** + * Create WeTTY server + * @module WeTTy + */ +import express from 'express'; +import gc from 'gc-stats'; +import { Gauge, collectDefaultMetrics } from 'prom-client'; +import { getCommand } from './server/command.js'; +import { gcMetrics } from './server/metrics.js'; +import { server } from './server/socketServer.js'; +import { spawn } from './server/spawn.js'; +import { + sshDefault, + serverDefault, + forceSSHDefault, + defaultCommand, +} from './shared/defaults.js'; +import { logger as getLogger } from './shared/logger.js'; +import type { SSH, SSL, Server } from './shared/interfaces.js'; +import type { Express } from 'express'; +import type SocketIO from 'socket.io'; + +export * from './shared/interfaces.js'; +export { logger as getLogger } from './shared/logger.js'; + +const wettyConnections = new Gauge({ + name: 'wetty_connections', + help: 'number of active socket connections to wetty', +}); + +/** + * Starts WeTTy Server + * @name startServer + * @returns Promise that resolves SocketIO server + */ +export const start = ( + ssh: SSH = sshDefault, + serverConf: Server = serverDefault, + command: string = defaultCommand, + forcessh: boolean = forceSSHDefault, + ssl: SSL | undefined = undefined, +): Promise => + decorateServerWithSsh(express(), ssh, serverConf, command, forcessh, ssl); + +export async function decorateServerWithSsh( + app: Express, + ssh: SSH = sshDefault, + serverConf: Server = serverDefault, + command: string = defaultCommand, + forcessh: boolean = forceSSHDefault, + ssl: SSL | undefined = undefined, +): Promise { + const logger = getLogger(); + if (ssh.key) { + logger.warn(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! Password-less auth enabled using private key from ${ssh.key}. +! This is dangerous, anything that reaches the wetty server +! will be able to run remote operations without authentication. +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`); + } + + collectDefaultMetrics(); + gc().on('stats', gcMetrics); + + const io = await server(app, serverConf, ssl); + /** + * Wetty server connected too + * @fires WeTTy#connnection + */ + io.on('connection', async (socket: SocketIO.Socket) => { + /** + * @event wetty#connection + * @name connection + */ + logger.info('Connection accepted.'); + wettyConnections.inc(); + + try { + const args = await getCommand(socket, ssh, command, forcessh); + logger.debug('Command Generated', { cmd: args.join(' ') }); + await spawn(socket, args); + } catch (error) { + logger.info('Disconnect signal sent', { err: error }); + wettyConnections.dec(); + } + }); + return io; +} diff --git a/wetty/src/server/command.ts b/wetty/src/server/command.ts new file mode 100644 index 0000000..242e4af --- /dev/null +++ b/wetty/src/server/command.ts @@ -0,0 +1,70 @@ +import process from 'node:process'; +import url from 'url'; +import _ from 'lodash'; +import { address } from './command/address.js'; +import { loginOptions } from './command/login.js'; +import { sshOptions } from './command/ssh.js'; +import type { SSH } from '../shared/interfaces'; +import type { Socket } from 'socket.io'; + +const localhost = (host: string): boolean => + !_.isUndefined(process.getuid) && + process.getuid() === 0 && + (host === 'localhost' || host === '0.0.0.0' || host === '127.0.0.1'); + +const urlArgs = ( + referer: string | undefined, + { + allowRemoteCommand, + allowRemoteHosts, + }: { + allowRemoteCommand: boolean; + allowRemoteHosts: boolean; + }, +): { [s: string]: string } => + _.pick( + _.pickBy(url.parse(referer || '', true).query, _.isString), + ['pass'], + allowRemoteCommand ? ['command', 'path'] : [], + allowRemoteHosts ? ['port', 'host'] : [], + ); + +export async function getCommand( + socket: Socket, + { + user, + host, + port, + auth, + pass, + key, + knownHosts, + config, + allowRemoteHosts, + allowRemoteCommand, + }: SSH, + command: string, + forcessh: boolean +): Promise { + const { + request: { headers: { referer } }, + client: { conn: { remoteAddress } }, + } = socket; + + if (!forcessh && localhost(host)) { + return loginOptions(command, remoteAddress); + } + + const sshAddress = await address(socket, user, host); + const args = { + host: sshAddress, + port: `${port}`, + pass: pass || '', + command, + auth, + knownHosts, + config: config || '', + ...urlArgs(referer, { allowRemoteHosts, allowRemoteCommand }), + }; + return sshOptions(args, key); +} diff --git a/wetty/src/server/command/address.ts b/wetty/src/server/command/address.ts new file mode 100644 index 0000000..30620d4 --- /dev/null +++ b/wetty/src/server/command/address.ts @@ -0,0 +1,32 @@ +import _ from 'lodash'; +import { Socket } from 'socket.io'; +import { login } from '../login.js'; +import { escapeShell } from '../shared/shell.js'; + +export async function address( + socket: Socket, + user: string, + host: string, +): Promise { + // Check request-header for username + const { request: { headers: { + 'remote-user': userFromHeader, + referer + } } } = socket; + + let username: string | undefined; + if (!_.isUndefined(userFromHeader) && !Array.isArray(userFromHeader)) { + username = userFromHeader; + } else { + const userFromPathMatch = referer?.match('.+/ssh/([^/]+)$'); + if (userFromPathMatch) { + // eslint-disable-next-line prefer-destructuring + username = userFromPathMatch[1].split('?')[0]; + } else if (user) { + username = user; + } else { + username = await login(socket); + } + } + return `${escapeShell(username)}@${host}`; +} diff --git a/wetty/src/server/command/login.ts b/wetty/src/server/command/login.ts new file mode 100644 index 0000000..805bf36 --- /dev/null +++ b/wetty/src/server/command/login.ts @@ -0,0 +1,12 @@ +import isUndefined from 'lodash/isUndefined.js'; + +const getRemoteAddress = (remoteAddress: string): string => + isUndefined(remoteAddress.split(':')[3]) + ? 'localhost' + : remoteAddress.split(':')[3]; + +export function loginOptions(command: string, remoteAddress: string): string[] { + return command === 'login' + ? [command, '-h', getRemoteAddress(remoteAddress)] + : [command]; +} diff --git a/wetty/src/server/command/ssh.ts b/wetty/src/server/command/ssh.ts new file mode 100644 index 0000000..160d586 --- /dev/null +++ b/wetty/src/server/command/ssh.ts @@ -0,0 +1,43 @@ +import isUndefined from 'lodash/isUndefined.js'; +import { logger } from '../../shared/logger.js'; + +export function sshOptions( + { + pass, + path, + command, + host, + port, + auth, + knownHosts, + config, + }: Record, + key?: string, +): string[] { + const cmd = parseCommand(command, path); + const hostChecking = knownHosts !== '/dev/null' ? 'yes' : 'no'; + logger().info(`Authentication Type: ${auth}`); + + return [ + ...pass ? ['sshpass', '-p', pass] : [], + 'ssh', + '-t', + ...config ? ['-F', config] : [], + ...port ? ['-p', port] : [], + ...key ? ['-i', key] : [], + ...auth !== 'none' ? ['-o', `PreferredAuthentications=${auth}`] : [], + '-o', `UserKnownHostsFile=${knownHosts}`, + '-o', `StrictHostKeyChecking=${hostChecking}`, + '-o', 'EscapeChar=none', + '--', + host, + ...cmd ? [cmd] : [], + ]; +} + +function parseCommand(command: string, path?: string): string { + if (command === 'login' && isUndefined(path)) return ''; + return !isUndefined(path) + ? `$SHELL -c "cd ${path};${command === 'login' ? '$SHELL' : command}"` + : command; +} diff --git a/wetty/src/server/flowcontrol.ts b/wetty/src/server/flowcontrol.ts new file mode 100644 index 0000000..1bb3b2e --- /dev/null +++ b/wetty/src/server/flowcontrol.ts @@ -0,0 +1,81 @@ +import { Socket } from 'socket.io'; + +/** + * tinybuffer to lower message pressure on the websocket. + * Incoming data from PTY will be held back at most for `timeout` microseconds. + * If the accumulated data exceeds `maxSize` the message will be sent + * immediately. + */ +export function tinybuffer(socket: Socket, timeout: number, maxSize: number) { + const s: string[] = []; + let length = 0; + let sender: NodeJS.Timeout | null = null; + return (data: string) => { + s.push(data); + length += data.length; + if (length > maxSize) { + socket.emit('data', s.join('')); + s.length = 0; + length = 0; + if (sender) { + clearTimeout(sender); + sender = null; + } + } + else if (!sender) { + sender = setTimeout(() => { + socket.emit('data', s.join('')); + s.length = 0; + length = 0; + sender = null; + }, timeout); + } + }; +} + +/** + * Flow control - server side. + * Does basic low to high watermark flow control. + * + * `account` should be fed by new chunk length and returns `true`, + * if the underlying PTY should be paused. + * + * `commit` should be fed by the length value of an 'ack' message + * indicating its final processing on xtermjs side. Returns `true` + * if the underlying PTY should be resumed. + * + * Note: Chosen values for low and high must be within reach of the + * chosen value of ackBytes on client side, otherwise + * flow control may block forever sooner or later. + * + * The default values are chosen quite high to lower negative impact on overall + * throughput. If you need snappier keyboard response under high data pressure + * (e.g. pressing Ctrl-C while `yes` is running), lower the values. + * This furthermore depends a lot on the general latency of your connection. + */ +export class FlowControlServer { + public counter = 0; + public low = 524288; // 2^19 --> 2x ackBytes from frontend + public high = 2097152; // 2^21 --> 8x ackBytes from frontend + + constructor(low?: number, high?: number) { + if (low) { + this.low = low; + } + if (high) { + this.high = high; + } + } + + public account(length: number): boolean { + const old = this.counter; + this.counter += length; + return old < this.high && this.counter > this.high; + } + + public commit(length: number): boolean { + const old = this.counter; + this.counter -= length; + return old > this.low && this.counter < this.low; + } +} diff --git a/wetty/src/server/login.ts b/wetty/src/server/login.ts new file mode 100644 index 0000000..649b83e --- /dev/null +++ b/wetty/src/server/login.ts @@ -0,0 +1,37 @@ +import { dirname, resolve as resolvePath } from 'path'; +import { fileURLToPath } from 'url'; +import pty from 'node-pty'; +import { xterm } from './shared/xterm.js'; +import type SocketIO from 'socket.io'; + +const executable = resolvePath( + dirname(fileURLToPath(import.meta.url)), + '..', + 'buffer.js', +); + +export function login(socket: SocketIO.Socket): Promise { + // Request carries no username information + // Create terminal and ask user for username + const term = pty.spawn('/usr/bin/env', ['node', executable], xterm); + let buf = ''; + return new Promise((resolve, reject) => { + term.onExit(({ exitCode }) => { + console.error(`Process exited with code: ${exitCode}`); + resolve(buf); + }); + term.onData((data: string) => { + socket.emit('data', data); + }); + socket + .on('input', (input: string) => { + term.write(input); + // eslint-disable-next-line no-control-regex + buf = /\x0177/.exec(input) ? buf.slice(0, -1) : buf + input; + }) + .on('disconnect', () => { + term.kill(); + reject(); + }); + }); +} diff --git a/wetty/src/server/metrics.ts b/wetty/src/server/metrics.ts new file mode 100644 index 0000000..19b9922 --- /dev/null +++ b/wetty/src/server/metrics.ts @@ -0,0 +1,42 @@ +import { Counter } from 'prom-client'; +import type { GCStatistics } from 'gc-stats'; + +const gcLabelNames = ['gctype']; +const gcTypes = { + 0: 'Unknown', + 1: 'Scavenge', + 2: 'MarkSweepCompact', + 3: 'ScavengeAndMarkSweepCompact', + 4: 'IncrementalMarking', + 8: 'WeakPhantom', + 15: 'All', +}; + +const gcCount = new Counter({ + name: `nodejs_gc_runs_total`, + help: 'Count of total garbage collections.', + labelNames: gcLabelNames, +}); + +const gcTimeCount = new Counter({ + name: `nodejs_gc_pause_seconds_total`, + help: 'Time spent in GC Pause in seconds.', + labelNames: gcLabelNames, +}); + +const gcReclaimedCount = new Counter({ + name: `nodejs_gc_reclaimed_bytes_total`, + help: 'Total number of bytes reclaimed by GC.', + labelNames: gcLabelNames, +}); + +export const gcMetrics = ({ gctype, diff, pause }: GCStatistics): void => { + const gcType = gcTypes[gctype]; + + gcCount.labels(gcType).inc(); + gcTimeCount.labels(gcType).inc(pause / 1e9); + + if (diff.usedHeapSize < 0) { + gcReclaimedCount.labels(gcType).inc(diff.usedHeapSize * -1); + } +}; diff --git a/wetty/src/server/shared/shell.spec.ts b/wetty/src/server/shared/shell.spec.ts new file mode 100644 index 0000000..ac40f4b --- /dev/null +++ b/wetty/src/server/shared/shell.spec.ts @@ -0,0 +1,25 @@ +import 'mocha'; +import { expect } from 'chai'; +import { escapeShell } from './shell'; + +describe('Values passed to escapeShell should be safe to pass woth sub processes', () => { + it('should escape remove subcommands', () => { + const cmd = escapeShell('test`echo hello`'); + expect(cmd).to.equal('testechohello'); + }); + + it('should allow usernames with special characters', () => { + const cmd = escapeShell('bob.jones\\COM@ultra-machine_dir'); + expect(cmd).to.equal('bob.jones\\COM@ultra-machine_dir'); + }); + + it('should ensure args cant be flags', () => { + const cmd = escapeShell("-oProxyCommand='bash' -c `wget localhost:2222`"); + expect(cmd).to.equal('oProxyCommandbash-cwgetlocalhost2222'); + }); + + it('should remove dashes even when there are illegal characters before them', () => { + const cmd = escapeShell("`-oProxyCommand='bash' -c `wget localhost:2222`"); + expect(cmd).to.equal('oProxyCommandbash-cwgetlocalhost2222'); + }); +}); diff --git a/wetty/src/server/shared/shell.ts b/wetty/src/server/shared/shell.ts new file mode 100644 index 0000000..e9a4310 --- /dev/null +++ b/wetty/src/server/shared/shell.ts @@ -0,0 +1,3 @@ +export const escapeShell = (username: string): string => + // eslint-disable-next-line no-useless-escape + username.replace(/[^a-zA-Z0-9_\\\-\.\@-]/g, '').replace(/^-+/g, ''); diff --git a/wetty/src/server/shared/xterm.ts b/wetty/src/server/shared/xterm.ts new file mode 100644 index 0000000..b4d4480 --- /dev/null +++ b/wetty/src/server/shared/xterm.ts @@ -0,0 +1,15 @@ +import isUndefined from 'lodash/isUndefined.js'; +import type { IPtyForkOptions } from 'node-pty'; + +export const xterm: IPtyForkOptions = { + name: 'xterm-256color', + cols: 80, + rows: 30, + cwd: process.cwd(), + env: Object.assign( + {}, + ...Object.keys(process.env) + .filter((key: string) => !isUndefined(process.env[key])) + .map((key: string) => ({ [key]: process.env[key] })), + ), +}; diff --git a/wetty/src/server/socketServer.ts b/wetty/src/server/socketServer.ts new file mode 100644 index 0000000..2dc9cf1 --- /dev/null +++ b/wetty/src/server/socketServer.ts @@ -0,0 +1,51 @@ +import compression from 'compression'; +import winston from 'express-winston'; +import { logger } from '../shared/logger.js'; +import { serveStatic, trim } from './socketServer/assets.js'; +import { html } from './socketServer/html.js'; +import { metricMiddleware, metricRoute } from './socketServer/metrics.js'; +import { favicon, redirect } from './socketServer/middleware.js'; +import { policies } from './socketServer/security.js'; +import { listen } from './socketServer/socket.js'; +import { loadSSL } from './socketServer/ssl.js'; +import type { SSL, SSLBuffer, Server } from '../shared/interfaces.js'; +import type { Express } from 'express'; +import type SocketIO from 'socket.io'; + +export async function server( + app: Express, + { base, port, host, title, allowIframe, socket }: Server, + ssl?: SSL, +): Promise { + const basePath = trim(base); + logger().info('Starting server', { + ssl, + port, + base, + title, + }); + + const client = html(basePath, title); + app + .disable('x-powered-by') + .use(metricMiddleware(basePath)) + .use(`${basePath}/metrics`, metricRoute) + .use(`${basePath}/client`, serveStatic('client')) + .use( + winston.logger({ + winstonInstance: logger(), + expressFormat: true, + level: 'http', + }), + ) + .use(compression()) + .use(await favicon(basePath)) + .use(redirect) + .use(policies(allowIframe)) + .get(basePath, client) + .get(`${basePath}/ssh/:user`, client); + + const sslBuffer: SSLBuffer = await loadSSL(ssl); + + return listen(app, host, port, basePath, sslBuffer, socket); +} diff --git a/wetty/src/server/socketServer/assets.ts b/wetty/src/server/socketServer/assets.ts new file mode 100644 index 0000000..f506063 --- /dev/null +++ b/wetty/src/server/socketServer/assets.ts @@ -0,0 +1,5 @@ +import serve from 'serve-static'; +import { assetsPath } from './shared/path.js'; + +export const trim = (str: string): string => str.replace(/\/*$/, ''); +export const serveStatic = (path: string) => serve(assetsPath(path)); diff --git a/wetty/src/server/socketServer/html.ts b/wetty/src/server/socketServer/html.ts new file mode 100644 index 0000000..9aceca0 --- /dev/null +++ b/wetty/src/server/socketServer/html.ts @@ -0,0 +1,124 @@ +import { isDev } from '../../shared/env.js'; +import type { Request, Response, RequestHandler } from 'express'; + +const jsFiles = isDev ? ['dev.js', 'wetty.js'] : ['wetty.js']; + +const render = ( + title: string, + base: string, +): string => ` + + + + + + + ${title} + + + +
+
+
+ +
+
+
+ + +
+ +
+ ${jsFiles + .map(file => ` `) + .join('\n') + } + +`; + +export const html = (base: string, title: string): RequestHandler => ( + _req: Request, + res: Response, +): void => { + res.send( + render( + title, + base, + ), + ); +}; diff --git a/wetty/src/server/socketServer/metrics.ts b/wetty/src/server/socketServer/metrics.ts new file mode 100644 index 0000000..279684c --- /dev/null +++ b/wetty/src/server/socketServer/metrics.ts @@ -0,0 +1,126 @@ +import url from 'url'; +import { register, Counter, Histogram } from 'prom-client'; +import ResponseTime from 'response-time'; +import UrlValueParser from 'url-value-parser'; +import type { Request, Response, RequestHandler } from 'express'; + +const requestLabels = ['route', 'method', 'status']; + +const requestCount = new Counter({ + name: 'http_requests_total', + help: 'Counter for total requests received', + labelNames: requestLabels, +}); + +const requestDuration = new Histogram({ + name: 'http_request_duration_seconds', + help: 'Duration of HTTP requests in seconds', + labelNames: requestLabels, + buckets: [0.01, 0.1, 0.5, 1, 1.5], +}); + +const requestLength = new Histogram({ + name: 'http_request_length_bytes', + help: 'Content-Length of HTTP request', + labelNames: requestLabels, + buckets: [512, 1024, 5120, 10240, 51200, 102400], +}); + +const responseLength = new Histogram({ + name: 'http_response_length_bytes', + help: 'Content-Length of HTTP response', + labelNames: requestLabels, + buckets: [512, 1024, 5120, 10240, 51200, 102400], +}); + +/** + * Normalizes urls paths. + * + * This function replaces route params like ids, with a placeholder, so we can + * set the metrics label, correctly. E.g., both routes + * + * - /api/v1/user/1 + * - /api/v1/user/2 + * + * represents the same logical route, and we want to group them together, + * hence the need for the normalization. + * + * @param {!string} path - url path. + * @param {string} [placeholder='#val'] - the placeholder that will replace id like params in the url path. + * @returns {string} a normalized path, withoud ids. + */ +function normalizePath(originalUrl: string, placeholder = '#val'): string { + const { pathname } = url.parse(originalUrl); + const urlParser = new UrlValueParser(); + return urlParser.replacePathValues(pathname || '', placeholder); +} + +/** + * Normalizes http status codes. + * + * Returns strings in the format (2|3|4|5)XX. + */ +function normalizeStatusCode(status: number): string { + if (status >= 200 && status < 300) { + return '2XX'; + } + + if (status >= 300 && status < 400) { + return '3XX'; + } + + if (status >= 400 && status < 500) { + return '4XX'; + } + + return '5XX'; +} + +export function metricMiddleware(basePath: string): RequestHandler { + const metricsPath = `${basePath}/metrics`; + + /** + * Corresponds to the R(equest rate), E(error rate), and D(uration of requests), + * of the RED metrics. + */ + return ResponseTime((req: Request, res: Response, time: number): void => { + const { originalUrl, method } = req; + // will replace ids from the route with `#val` placeholder this serves to + // measure the same routes, e.g., /image/id1, and /image/id2, will be + // treated as the same route + const route = normalizePath(originalUrl); + + if (route !== metricsPath) { + const labels = { + route, + method, + status: normalizeStatusCode(res.statusCode), + }; + + requestCount.inc(labels); + + // observe normalizing to seconds + requestDuration.observe(labels, time / 1000); + + // observe request length + const reqLength = req.get('Content-Length'); + if (reqLength) { + requestLength.observe(labels, Number(reqLength)); + } + + // observe response length + const resLength = res.get('Content-Length'); + if (resLength) { + responseLength.observe(labels, Number(resLength)); + } + } + }); +} + +/** + * Metrics route to be used by prometheus to scrape metrics + */ +export async function metricRoute(_req: Request, res: Response): Promise { + res.set('Content-Type', register.contentType); + res.end(await register.metrics()); +} diff --git a/wetty/src/server/socketServer/middleware.ts b/wetty/src/server/socketServer/middleware.ts new file mode 100644 index 0000000..bd59dc3 --- /dev/null +++ b/wetty/src/server/socketServer/middleware.ts @@ -0,0 +1,99 @@ +import etag from 'etag'; +import fresh from 'fresh'; +import fs from 'fs-extra'; +import parseUrl from 'parseurl'; +import { assetsPath } from './shared/path.js'; +import type { Request, Response, NextFunction, RequestHandler } from 'express'; + +const ONE_YEAR_MS = 60 * 60 * 24 * 365 * 1000; // 1 year + +/** + * Determine if the cached representation is fresh. + * @param req - server request + * @param res - server response + * @returns if the cache is fresh or not + */ +const isFresh = (req: Request, res: Response): boolean => + fresh(req.headers, { + etag: res.getHeader('ETag'), + 'last-modified': res.getHeader('Last-Modified'), + }); + +/** + * redirect requests with trailing / to remove it + * + * @param req - server request + * @param res - server response + * @param next - next middleware to call on finish + */ +export function redirect( + req: Request, + res: Response, + next: NextFunction, +): void { + if (req.path.substr(-1) === '/' && req.path.length > 1) + res.redirect(301, req.path.slice(0, -1) + req.url.slice(req.path.length)); + else next(); +} + +/** + * Serves the favicon located by the given `path`. + * + * @param basePath - server base path + * @returns middleware + */ +export async function favicon(basePath: string): Promise { + const path = assetsPath('client', 'favicon.ico'); + + try { + const icon = await fs.readFile(path); + return (req: Request, res: Response, next: NextFunction): void => { + if (getPathName(req) !== `${basePath}/client/favicon.ico`) { + next(); + } else if (req.method !== 'GET' && req.method !== 'HEAD') { + res.statusCode = req.method === 'OPTIONS' ? 200 : 405; + res.setHeader('Allow', 'GET, HEAD, OPTIONS'); + res.setHeader('Content-Length', '0'); + res.end(); + } else { + Object.entries({ + 'Cache-Control': `public, max-age=${Math.floor(ONE_YEAR_MS / 1000)}`, + ETag: etag(icon), + }).forEach(([key, value]) => { + res.setHeader(key, value); + }); + + // Validate freshness + if (isFresh(req, res)) { + res.statusCode = 304; + res.end(); + } else { + // Send icon + res.statusCode = 200; + res.setHeader('Content-Length', icon.length); + res.setHeader('Content-Type', 'image/x-icon'); + res.end(icon); + } + } + }; + } catch (err) { + return (_req: Request, _res: Response, next: NextFunction): void => + next(err); + } +} + +/** + * Get the request pathname. + * + * @param requests + * @returns path name or undefined + */ + +function getPathName(req: Request): string | undefined { + try { + const url = parseUrl(req); + return url?.pathname ? url.pathname : undefined; + } catch (e) { + return undefined; + } +} diff --git a/wetty/src/server/socketServer/security.ts b/wetty/src/server/socketServer/security.ts new file mode 100644 index 0000000..9c6b8a3 --- /dev/null +++ b/wetty/src/server/socketServer/security.ts @@ -0,0 +1,26 @@ +import helmet from 'helmet'; +import type { Request, Response } from 'express'; + +export const policies = + (allowIframe: boolean) => + (req: Request, res: Response, next: (err?: unknown) => void): void => { + const args: Record = { + referrerPolicy: { policy: ['no-referrer-when-downgrade'] }, + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"], + styleSrc: ["'self'", "'unsafe-inline'"], + fontSrc: ["'self'", 'data:'], + connectSrc: [ + "'self'", + (req.protocol === 'http' ? 'ws://' : 'wss://') + req.get('host'), + ], + }, + }, + frameguard: false + }; + if (!allowIframe) args.frameguard = { action: 'sameorigin' }; + + helmet(args)(req, res, next); + }; diff --git a/wetty/src/server/socketServer/shared/path.ts b/wetty/src/server/socketServer/shared/path.ts new file mode 100644 index 0000000..a94f01c --- /dev/null +++ b/wetty/src/server/socketServer/shared/path.ts @@ -0,0 +1,12 @@ +import { resolve, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import findUp from 'find-up'; + +const filePath = dirname( + findUp.sync('package.json', { + cwd: dirname(fileURLToPath(import.meta.url)), + }) || process.cwd(), +); + +export const assetsPath = (...args: string[]) => + resolve(filePath, 'build', ...args); diff --git a/wetty/src/server/socketServer/socket.ts b/wetty/src/server/socketServer/socket.ts new file mode 100644 index 0000000..98b3d24 --- /dev/null +++ b/wetty/src/server/socketServer/socket.ts @@ -0,0 +1,43 @@ +import http from 'http'; +import https from 'https'; +import isUndefined from 'lodash/isUndefined.js'; +import { Server } from 'socket.io'; + +import { logger } from '../../shared/logger.js'; +import type { SSLBuffer } from '../../shared/interfaces.js'; +import type express from 'express'; + +export const listen = ( + app: express.Express, + host: string, + port: number, + path: string, + { key, cert }: SSLBuffer, + socket?: string | boolean +): Server =>{ + // Create the base HTTP/HTTPS server + const server = !isUndefined(key) && !isUndefined(cert) + ? https.createServer({ key, cert }, app) + : http.createServer(app); + + // Start listening on either Unix socket or TCP + if (socket) { + server.listen(socket, () => { + logger().info('Server listening on Unix socket', { socket }); + }); + } else { + server.listen(port, host, () => { + logger().info('Server started', { + port, + connection: !isUndefined(key) && !isUndefined(cert) ? 'https' : 'http', + }); + }); + } + + // Create Socket.IO server + return new Server(server, { + path: `${path}/socket.io`, + pingInterval: 3000, + pingTimeout: 7000, + }); +} diff --git a/wetty/src/server/socketServer/ssl.ts b/wetty/src/server/socketServer/ssl.ts new file mode 100644 index 0000000..02154e1 --- /dev/null +++ b/wetty/src/server/socketServer/ssl.ts @@ -0,0 +1,14 @@ +import { resolve } from 'path'; +import fs from 'fs-extra'; +import isUndefined from 'lodash/isUndefined.js'; +import type { SSL, SSLBuffer } from '../../shared/interfaces'; + +export async function loadSSL(ssl?: SSL): Promise { + if (isUndefined(ssl) || isUndefined(ssl.key) || isUndefined(ssl.cert)) + return {}; + const [key, cert]: Buffer[] = await Promise.all([ + fs.readFile(resolve(ssl.key)), + fs.readFile(resolve(ssl.cert)), + ]); + return { key, cert }; +} diff --git a/wetty/src/server/spawn.ts b/wetty/src/server/spawn.ts new file mode 100644 index 0000000..7f1d759 --- /dev/null +++ b/wetty/src/server/spawn.ts @@ -0,0 +1,54 @@ +import isUndefined from 'lodash/isUndefined.js'; +import pty from 'node-pty'; +import { logger as getLogger } from '../shared/logger.js'; +import { tinybuffer, FlowControlServer } from './flowcontrol.js'; +import { xterm } from './shared/xterm.js'; +import { envVersionOr } from './spawn/env.js'; +import type SocketIO from 'socket.io'; + +export async function spawn( + socket: SocketIO.Socket, + args: string[], +): Promise { + const logger = getLogger(); + const version = await envVersionOr(0); + const cmd = version >= 9 ? ['-S', ...args] : args; + logger.debug('Spawning PTY', { cmd }); + const term = pty.spawn('/usr/bin/env', cmd, xterm); + const { pid } = term; + const address = args[0] === 'ssh' ? args[1] : 'localhost'; + logger.info('Process Started on behalf of user', { pid, address }); + socket.emit('login'); + term.onExit(({exitCode}) => { + logger.info('Process exited', { exitCode, pid }); + socket.emit('logout'); + socket + .removeAllListeners('disconnect') + .removeAllListeners('resize') + .removeAllListeners('input'); + }); + const send = tinybuffer(socket, 2, 524288); + const fcServer = new FlowControlServer(); + term.onData((data: string) => { + send(data); + if (fcServer.account(data.length)) { + term.pause(); + } + }); + socket + .on('resize', ({ cols, rows }) => { + term.resize(cols, rows); + }) + .on('input', input => { + if (!isUndefined(term)) term.write(input); + }) + .on('disconnect', () => { + term.kill(); + logger.info('Process exited', { code: 0, pid }); + }) + .on('commit', size => { + if (fcServer.commit(size)) { + term.resume(); + } + }); +} diff --git a/wetty/src/server/spawn/env.ts b/wetty/src/server/spawn/env.ts new file mode 100644 index 0000000..a566259 --- /dev/null +++ b/wetty/src/server/spawn/env.ts @@ -0,0 +1,22 @@ +import { exec } from 'child_process'; + +const envVersion = (): Promise => + new Promise((resolve, reject) => { + exec('/usr/bin/env --version', (error, stdout, stderr): void => { + if (error) { + return reject(Error(`error getting env version: ${error.message}`)); + } + if (stderr) { + return reject(Error(`error getting env version: ${stderr}`)); + } + return resolve( + parseInt( + stdout.split(/\r?\n/)[0].split(' (GNU coreutils) ')[1].split('.')[0], + 10, + ), + ); + }); + }); + +export const envVersionOr = (fallback: number): Promise => + envVersion().catch(() => fallback); diff --git a/wetty/src/shared/config.ts b/wetty/src/shared/config.ts new file mode 100644 index 0000000..c65549f --- /dev/null +++ b/wetty/src/shared/config.ts @@ -0,0 +1,160 @@ +import path from 'path'; +import fs from 'fs-extra'; +import JSON5 from 'json5'; +import isUndefined from 'lodash/isUndefined.js'; +import { + sshDefault, + serverDefault, + forceSSHDefault, + defaultCommand, + defaultLogLevel, +} from './defaults.js'; +import type { Config, SSH, Server, SSL } from './interfaces'; +import type winston from 'winston'; +import type { Arguments } from 'yargs'; + +type confValue = + | boolean + | string + | number + | undefined + | unknown + | SSH + | Server + | SSL; + +/** + * Cast given value to boolean + * + * @param value - variable to cast + * @returns variable cast to boolean + */ +function ensureBoolean(value: confValue): boolean { + switch (value) { + case true: + case 'true': + case 1: + case '1': + case 'on': + case 'yes': + return true; + default: + return false; + } +} + +function parseLogLevel( + confLevel: typeof winston.level, + optsLevel: unknown, +): typeof winston.level { + const logLevel = isUndefined(optsLevel) ? confLevel : `${optsLevel}`; + return [ + 'error', + 'warn', + 'info', + 'http', + 'verbose', + 'debug', + 'silly', + ].includes(logLevel) + ? (logLevel as typeof winston.level) + : defaultLogLevel; +} + +/** + * Load JSON5 config from file and merge with default args + * If no path is provided the default config is returned + * + * @param filepath - path to config to load + * @returns variable cast to boolean + */ +export async function loadConfigFile(filepath?: string): Promise { + if (isUndefined(filepath)) { + return { + ssh: sshDefault, + server: serverDefault, + command: defaultCommand, + forceSSH: forceSSHDefault, + logLevel: defaultLogLevel, + }; + } + const content = await fs.readFile(path.resolve(filepath)); + const parsed = JSON5.parse(content.toString()) as Config; + return { + ssh: isUndefined(parsed.ssh) + ? sshDefault + : Object.assign(sshDefault, parsed.ssh), + server: isUndefined(parsed.server) + ? serverDefault + : Object.assign(serverDefault, parsed.server), + command: isUndefined(parsed.command) ? defaultCommand : `${parsed.command}`, + forceSSH: isUndefined(parsed.forceSSH) + ? forceSSHDefault + : ensureBoolean(parsed.forceSSH), + ssl: parsed.ssl, + logLevel: parseLogLevel(defaultLogLevel, parsed.logLevel), + }; +} + +/** + * Merge 2 objects removing undefined fields + * + * @param target - base object + * @param source - object to get new values from + * @returns merged object + * + */ +const objectAssign = ( + target: SSH | Server, + source: Record, +): SSH | Server => + Object.fromEntries( + Object.entries(source).map(([key, value]) => [ + key, + isUndefined(source[key]) ? target[key] : value, + ]), + ) as SSH | Server; + +/** + * Merge cli arguemens with config object + * + * @param opts - Object containing cli args + * @param config - Config object + * @returns merged configuration + * + */ +export function mergeCliConf(opts: Arguments, config: Config): Config { + const ssl = { + key: opts['ssl-key'], + cert: opts['ssl-cert'], + ...config.ssl, + } as SSL; + return { + ssh: objectAssign(config.ssh, { + user: opts['ssh-user'], + host: opts['ssh-host'], + auth: opts['ssh-auth'], + port: opts['ssh-port'], + pass: opts['ssh-pass'], + key: opts['ssh-key'], + allowRemoteHosts: opts['allow-remote-hosts'], + allowRemoteCommand: opts['allow-remote-command'], + config: opts['ssh-config'], + knownHosts: opts['known-hosts'], + }) as SSH, + server: objectAssign(config.server, { + base: opts.base, + host: opts.host, + socket: opts.socket, + port: opts.port, + title: opts.title, + allowIframe: opts['allow-iframe'], + }) as Server, + command: isUndefined(opts.command) ? config.command : `${opts.command}`, + forceSSH: isUndefined(opts['force-ssh']) + ? config.forceSSH + : ensureBoolean(opts['force-ssh']), + ssl: isUndefined(ssl.key) || isUndefined(ssl.cert) ? undefined : ssl, + logLevel: parseLogLevel(config.logLevel, opts['log-level']), + }; +} diff --git a/wetty/src/shared/defaults.ts b/wetty/src/shared/defaults.ts new file mode 100644 index 0000000..33fa0a4 --- /dev/null +++ b/wetty/src/shared/defaults.ts @@ -0,0 +1,28 @@ +import { isDev } from './env.js'; +import type { SSH, Server } from './interfaces'; + +export const sshDefault: SSH = { + user: process.env.SSHUSER || '', + host: process.env.SSHHOST || 'localhost', + auth: process.env.SSHAUTH || 'password', + pass: process.env.SSHPASS || undefined, + key: process.env.SSHKEY || undefined, + port: parseInt(process.env.SSHPORT || '22', 10), + knownHosts: process.env.KNOWNHOSTS || '/dev/null', + allowRemoteHosts: false, + allowRemoteCommand: false, + config: process.env.SSHCONFIG || undefined, +}; + +export const serverDefault: Server = { + base: process.env.BASE || '/wetty/', + port: parseInt(process.env.PORT || '3000', 10), + host: '0.0.0.0', + socket: false, + title: process.env.TITLE || 'WeTTY - The Web Terminal Emulator', + allowIframe: process.env.ALLOWIFRAME === 'true' || false, +}; + +export const forceSSHDefault = process.env.FORCESSH === 'true' || false; +export const defaultCommand = process.env.COMMAND || 'login'; +export const defaultLogLevel = isDev ? 'debug' : 'http'; diff --git a/wetty/src/shared/env.ts b/wetty/src/shared/env.ts new file mode 100644 index 0000000..0bd4ecf --- /dev/null +++ b/wetty/src/shared/env.ts @@ -0,0 +1 @@ +export const isDev = process.env.NODE_ENV === 'development'; diff --git a/wetty/src/shared/interfaces.ts b/wetty/src/shared/interfaces.ts new file mode 100644 index 0000000..249739e --- /dev/null +++ b/wetty/src/shared/interfaces.ts @@ -0,0 +1,44 @@ +import type winston from 'winston'; + +export interface SSH { + [s: string]: string | number | boolean | undefined; + user: string; + host: string; + auth: string; + port: number; + knownHosts: string; + allowRemoteHosts: boolean; + allowRemoteCommand: boolean; + pass?: string; + key?: string; + config?: string; +} + +export interface SSL { + key: string; + cert: string; +} + +export interface SSLBuffer { + key?: Buffer; + cert?: Buffer; +} + +export interface Server { + [s: string]: string | number | boolean; + port: number; + host: string; + socket: string | boolean; + title: string; + base: string; + allowIframe: boolean; +} + +export interface Config { + ssh: SSH; + server: Server; + forceSSH: boolean; + command: string; + logLevel: typeof winston.level; + ssl?: SSL; +} diff --git a/wetty/src/shared/logger.ts b/wetty/src/shared/logger.ts new file mode 100644 index 0000000..296ef29 --- /dev/null +++ b/wetty/src/shared/logger.ts @@ -0,0 +1,40 @@ +import winston from 'winston'; +import { defaultLogLevel } from './defaults.js'; +import { isDev } from './env.js'; + +const { combine, timestamp, label, simple, json, colorize } = winston.format; + +const dev = combine( + colorize(), + label({ label: 'Wetty' }), + timestamp(), + simple(), +); + +const prod = combine(label({ label: 'Wetty' }), timestamp(), json()); + +let globalLogger = winston.createLogger({ + format: isDev ? dev : prod, + transports: [ + new winston.transports.Console({ + level: defaultLogLevel, + handleExceptions: true, + }), + ], +}); + +export function setLevel(level: typeof winston.level): void { + globalLogger = winston.createLogger({ + format: isDev ? dev : prod, + transports: [ + new winston.transports.Console({ + level, + handleExceptions: true, + }), + ], + }); +} + +export function logger(): winston.Logger { + return globalLogger; +} diff --git a/wetty/terminal.config.json b/wetty/terminal.config.json new file mode 100644 index 0000000..b62380e --- /dev/null +++ b/wetty/terminal.config.json @@ -0,0 +1,19 @@ +{ + "gatewayUrl": "wss://shell.biboer.cn:5173", + "gatewayToken": "remoteconn-dev-token", + "selectedServerId": "dev-server", + "servers": [ + { + "id": "dev-server", + "name": "Dev Server", + "host": "shell.biboer.cn", + "port": 22, + "username": "gavin", + "transportMode": "gateway", + "authType": "password", + "password": "Gavin123", + "cols": 80, + "rows": 24 + } + ] +} diff --git a/wetty/todo.md b/wetty/todo.md new file mode 100644 index 0000000..2c12e58 --- /dev/null +++ b/wetty/todo.md @@ -0,0 +1,23 @@ + +改造client直接接入网关,网关的配置信息,ssh服务器信息读取:terminal.config.json + +网关使用~/remoteconn现有网关,gateway在本地,wss协议,使用8787端口。外部访问web使用https://shell.biboer.cn:5173。 + +使用shell.biboer.cn这个域名, npm run dev后,使用https://shell.biboer.cn:5173访问。 + +Vite 开发服务本身走 HTTPS。 + +证书和私钥: +const DEV_PUBLIC_HOST = "shell.biboer.cn"; +const DEV_CERT_PATH = "/Users/gavin/.acme.sh/shell.biboer.cn_ecc/fullchain.cer"; +const DEV_KEY_PATH = "/Users/gavin/.acme.sh/shell.biboer.cn_ecc/shell.biboer.cn.key"; + +--- + +底部增加一个工具条,包括上下左右,tab,enter,ctrlc功能. +1. 键盘展开工具条贴在地址胶囊之上. +注意保持: +1. 地址胶囊和工具条保持透明 +2. 弹出键盘后,输入行的位置计算现在运行良好,不能受迭代影响. + + diff --git a/wetty/tsconfig.browser.json b/wetty/tsconfig.browser.json new file mode 100644 index 0000000..787ca12 --- /dev/null +++ b/wetty/tsconfig.browser.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "lib": ["DOM"], + "noEmit": true, + "incremental": true + }, + "include": ["src/client"] +} diff --git a/wetty/tsconfig.json b/wetty/tsconfig.json new file mode 100644 index 0000000..21ec45c --- /dev/null +++ b/wetty/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "es2020", + "target": "es2019", + "moduleResolution": "node", + "declaration": true, + "downlevelIteration": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "removeComments": true, + "skipLibCheck": true, + "strict": true + } +} diff --git a/wetty/tsconfig.node.json b/wetty/tsconfig.node.json new file mode 100644 index 0000000..e0b6049 --- /dev/null +++ b/wetty/tsconfig.node.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "incremental": true, + "outDir": "./build", + "sourceMap": true + }, + "include": [ + "src" + ], + "exclude": [ + "src/client" + ] +} diff --git a/wetty/vite.config.ts b/wetty/vite.config.ts new file mode 100644 index 0000000..f006784 --- /dev/null +++ b/wetty/vite.config.ts @@ -0,0 +1,81 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { defineConfig } from 'vite'; + +const DEV_PUBLIC_HOST = 'shell.biboer.cn'; +const DEV_CERT_PATH = + '/Users/gavin/.acme.sh/shell.biboer.cn_ecc/fullchain.cer'; +const DEV_KEY_PATH = + '/Users/gavin/.acme.sh/shell.biboer.cn_ecc/shell.biboer.cn.key'; +const TERMINAL_CONFIG_FILE = path.resolve(process.cwd(), 'terminal.config.json'); + +function readTLSFile(path: string): Buffer { + if (!fs.existsSync(path)) { + throw new Error(`开发证书文件不存在: ${path}`); + } + return fs.readFileSync(path); +} + +export default defineConfig({ + plugins: [ + { + name: 'serve-terminal-config', + configureServer(server) { + // 开发态从仓库根目录实时读取 terminal.config.json,避免手动复制到 public。 + server.middlewares.use((req, res, next) => { + const reqPath = (req.url || '').split('?')[0]; + if (reqPath !== '/terminal.config.json') { + next(); + return; + } + + if (!fs.existsSync(TERMINAL_CONFIG_FILE)) { + res.statusCode = 404; + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + res.end( + JSON.stringify({ + error: 'terminal.config.json 不存在', + path: TERMINAL_CONFIG_FILE, + }), + ); + return; + } + + const content = fs.readFileSync(TERMINAL_CONFIG_FILE, 'utf8'); + res.statusCode = 200; + res.setHeader('Cache-Control', 'no-store'); + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + res.end(content); + }); + }, + }, + ], + server: { + host: '0.0.0.0', + port: 5173, + strictPort: true, + allowedHosts: [DEV_PUBLIC_HOST], + https: { + cert: readTLSFile(DEV_CERT_PATH), + key: readTLSFile(DEV_KEY_PATH), + }, + hmr: { + protocol: 'wss', + host: DEV_PUBLIC_HOST, + port: 5173, + }, + proxy: { + // 浏览器通过 https://shell.biboer.cn:5173 访问时,这里将 wss 升级请求转发到本地网关。 + '/ws/terminal': { + target: 'ws://127.0.0.1:8787', + ws: true, + changeOrigin: true, + }, + '/ws/asr': { + target: 'ws://127.0.0.1:8787', + ws: true, + changeOrigin: true, + }, + }, + }, +}); diff --git a/wetty/xterminal-wetty-diff.md b/wetty/xterminal-wetty-diff.md new file mode 100644 index 0000000..aad91ac --- /dev/null +++ b/wetty/xterminal-wetty-diff.md @@ -0,0 +1,318 @@ +# xterminal 与 wetty client 详细差异对比 + +## 1. 对比范围与结论 + +本文对比对象: + +- `xterminal`:`/Users/gavin/lab/xterminal` +- `wetty` 当前项目前端 client:`/Users/gavin/lab/wetty/src/client/wetty/**`(以及其页面与配置面板) + +核心结论: + +1. 两者不是同层替代关系。`xterminal` 是前端终端组件库,`wetty client` 是完整 Web SSH 网关客户端。 +2. `wetty client` 的核心能力来自 `xterm.js` 真终端仿真;`xterminal` 核心是“CLI 交互组件”,终端协议能力主要由业务层补齐。 +3. 若目标是“做 SSH/PTY 级别会话”,`wetty client` 路线更直接;若目标是“嵌入式 Web CLI 组件”,`xterminal` 更轻更可控。 + +--- + +## 2. 产品定位与边界 + +### xterminal(组件库定位) + +- 对外是 `XTerminal` 类 API(`mount/write/history/setCompleter/pause/resume/dispose`)。 +- 不绑定任何固定后端协议,网络连接由业务方自行实现。 +- 证据: + - `source/index.ts:14`(类定义) + - `source/index.ts:95`(挂载) + - `source/index.ts:53`(输出 API) + - `source/index.ts:90`(补全) + +### wetty client(业务客户端定位) + +- 页面入口直接完成:终端初始化、断线重连 UI、网关连接、数据收发。 +- 与运行配置、网关协议、页面 DOM 强耦合。 +- 证据: + - `src/client/wetty.ts:26`(启动终端) + - `src/client/wetty.ts:38`(socket 事件链) + - `src/client/wetty/socket.ts:43`(网关 socket 实现) + +--- + +## 3. 渲染与终端仿真能力 + +### wetty client:xterm.js 终端仿真 + +- 直接继承 `@xterm/xterm` 的 `Terminal`。 +- 加载了 `FitAddon`、`WebLinksAddon`、`ImageAddon`。 +- 原生具备终端仿真语义(光标、控制序列、窗口 resize、链接识别等)。 +- 证据: + - `src/client/wetty/term.ts:13` + - `src/client/wetty/term.ts:21` + - `src/client/wetty/term.ts:23` + - `src/client/wetty/term.ts:24` + +### xterminal:组件渲染模型 + +- 输出层以 HTML 片段写入为主。 +- 核心库不自带完整 VT/ANSI 终端协议仿真。 +- ANSI 处理与网关协议示例主要存在于 demo 脚本,不在库核心 API。 +- 证据: + - `source/output/index.ts:40` + - `source/output/index.ts:49` + - `demo/main.js:381`(ANSI 转换) + +--- + +## 4. 输入模型差异(交互体验关键) + +### wetty client:按键流(streaming input) + +- 用户每次按键通过 `term.onData` 立即发送到后端。 +- 更接近真实 SSH TTY 行为(如逐字符编辑、交互式全屏程序)。 +- 证据: + - `src/client/wetty.ts:60` + +### xterminal:行输入(line-based) + +- 默认在 `Enter` 时触发 `data` 事件并提交一整行。 +- 上下箭头与历史命令是组件内建行为。 +- 证据: + - `source/instance.ts:136`(Enter 分支) + - `source/instance.ts:141`(emit data) + - `source/instance.ts:147`(历史导航) + +影响: + +1. 需要 PTY 级“实时按键”时,`xterminal` 必须由业务层额外桥接按键事件。 +2. 需要“类 REPL 文本命令输入”时,`xterminal` 默认体验更简单。 + +--- + +## 5. 协议与连接层 + +### wetty client:内置网关协议适配 + +- 内建 `GatewaySocket`,封装事件 `connect/login/data/logout/disconnect/error`。 +- 支持 `init/stdin/resize/control` 帧。 +- 自动处理 `ping/pong`、`connected`、`disconnect` 控制帧。 +- 证据: + - `src/client/wetty/socket.ts:3` + - `src/client/wetty/socket.ts:57` + - `src/client/wetty/socket.ts:103` + - `src/client/wetty/socket.ts:238` + +### xterminal:协议在业务层(demo)实现 + +- 核心库无 WebSocket 协议绑定。 +- demo 中手工实现网关连接、帧发送、回包处理、排队与批量 flush。 +- 证据: + - `demo/main.js:635`(connectByConfig) + - `demo/main.js:572`(handleGatewayFrame) + - `demo/main.js:501`(stdin 发送) + +--- + +## 6. 配置系统与可配置能力 + +### wetty client + +- 运行时读取 `/terminal.config.json`,并做最小字段校验、选中 server 逻辑。 +- 有 iframe 配置面板,可改大量 xterm 选项并持久化 `localStorage.options`。 +- 证据: + - `src/client/wetty/runtimeConfig.ts:77` + - `src/client/wetty/runtimeConfig.ts:107` + - `src/client/wetty/term/confiruragtion.ts:8` + - `src/client/wetty/term/load.ts:10` + - `src/assets/xterm_config/index.html:62` + +### xterminal + +- 核心库配置简单(主要是挂载 target)。 +- 无内建“终端参数 GUI 编辑器”,这部分由业务层自行实现。 +- 证据: + - `source/types.d.ts:103` + +--- + +## 7. 移动端策略对比 + +### wetty client + +- 对 xterm 屏幕加 `contenteditable` 以触发软键盘。 +- 页面内置功能键浮层(方向键、Esc、Ctrl、Tab)。 +- 证据: + - `src/client/wetty/mobile.ts:3` + - `index.html:28` + - `src/client/wetty/term.ts:190` + +### xterminal + +- 核心对移动端做了更系统处理: + - 隐藏 textarea + 自绘光标 + - `visualViewport` 监听,动态补偿软键盘遮挡 + - 避免 iOS Safari 聚焦导致 body 整体上推 +- 证据: + - `source/instance.ts:58` + - `source/renderer/index.ts:27` + - `theme/index.css:68` + +结论: + +1. `wetty` 的移动端更偏“终端页面增强”。 +2. `xterminal` 的移动端更偏“组件内部自洽”。 + +--- + +## 8. 安全与风险点 + +### 8.1 前端渲染安全 + +- `xterminal` 提供 `writeSafe/escapeHTML`,明确区分不可信文本输出路径。 +- 证据: + - `source/index.ts:61` + - `source/output/index.ts:19` + +- `wetty` 断线信息使用 `innerHTML` 注入文本,若消息来源不可信有注入风险。 +- 证据: + - `src/client/wetty/disconnect.ts:9` + +### 8.2 配置泄露风险 + +- 两边当前 `terminal.config.json` 都包含明文密码,属于高风险配置习惯(即使是开发环境)。 +- 证据: + - `/Users/gavin/lab/wetty/terminal.config.json` + - `/Users/gavin/lab/xterminal/terminal.config.json` + +--- + +## 9. 特性差异清单(功能维度) + +### wetty client 独有/更强 + +1. xterm.js 终端仿真与 addon 生态。 +2. 现成文件下载控制序列解析(`ESC[5i` / `ESC[4i`)。 +3. 完整连接态 UI(overlay、reconnect、配置侧栏)。 +4. 网关初始化载荷构造(含证书/私钥/密码多认证类型)。 + +证据: + +- `src/client/wetty/download.ts:4` +- `src/client/wetty/download.ts:76` +- `src/client/wetty/socket.ts:161` +- `index.html:14` + +### xterminal 独有/更强 + +1. 组件化 API 清晰,易嵌入任意前端。 +2. 事件总线与资源释放边界明确(`dispose`、`register`)。 +3. 历史记录与补全接口简洁。 +4. 测试覆盖相对更系统(input/output/history/emitter/xterminal)。 + +证据: + +- `source/index.ts:117` +- `source/emitter/index.ts:16` +- `tests/xterminal.test.ts:4` +- `tests/input.test.ts:5` + +--- + +## 10. 工程化与依赖体量 + +### 依赖与发布目标 + +- `wetty`: + - 运行时依赖较重(含 `@xterm/*`、`express`、`socket.io`、`node-pty` 等)。 + - 发布目标是应用(`bin/main` + server + client)。 + - 证据:`package.json:44`(dependencies) + +- `xterminal`: + - 核心 runtime 无 dependencies(仅 devDependencies)。 + - 发布目标是库(UMD + ESM + CSS + types)。 + - 证据:`package.json:64`(devDependencies)、`rollup.config.js:31` + +### 产物规模(当前工作区构建结果) + +- `wetty` client bundle:`build/client/wetty.js` 约 `511K`(含 source map 更大)。 +- `xterminal` dist:`xterminal.esm.js` 约 `8.5K`,`xterminal.umd.js` 约 `16K`。 + +结论: + +1. 若只需要嵌入命令交互 UI,`xterminal` 体量优势明显。 +2. 若需要终端仿真,`wetty`/xterm.js 的体量属于能力成本。 + +--- + +## 11. 测试对比 + +- `wetty` 当前仓库测试集中在: + - 客户端 `FileDownloader` + - 服务端 shell escaping + - 证据:`src/client/wetty/download.spec.ts:10`、`src/server/shared/shell.spec.ts:5` + +- `xterminal` 测试覆盖: + - `xterminal/input/output/history/emitter/disposable/reactivity` + - 证据:`/Users/gavin/lab/xterminal/tests/*.test.ts` + +--- + +## 12. 当前项目内一个关键不一致点 + +在当前 `wetty` 仓库中,前端 client 已改为“网关 WebSocket 帧协议”,但后端原有代码仍是 Socket.IO 事件模型(`input/resize/commit`)。 + +- 前端证据:`src/client/wetty/socket.ts:97` +- 后端证据:`src/server/spawn.ts:38`、`src/server/socketServer/socket.ts:38` + +这意味着: + +1. 当前 client 与本仓库原 server 不天然对接。 +2. client 实际更像在对接外部网关服务,而不是本地 wetty server。 + +--- + +## 13. 选型建议(按目标场景) + +### 选 wetty client 路线 + +适合: + +1. 目标是“浏览器里完整 SSH 终端”。 +2. 需要 xterm.js 级别兼容能力(控制序列、终端行为一致性)。 +3. 接受更重依赖与更复杂调试链路。 + +### 选 xterminal 路线 + +适合: + +1. 目标是“可嵌入、可定制、轻量 CLI 组件”。 +2. 协议、渲染、会话行为希望完全可控。 +3. 可接受自行补齐 SSH/ANSI/PTY 的业务层逻辑。 + +--- + +## 14. 如果要做“xterminal + wetty 能力融合”,推荐最小改造路径 + +1. 保持 `xterminal` 作为 UI 输入输出壳,不直接替换为 xterm.js。 +2. 单独抽出 `gateway client` 模块(`connect/send/receive/reconnect`),不要继续写在 demo/page 脚本。 +3. 定义统一帧协议适配层,把 `init/stdin/resize/control/stdout/stderr/error` 规范化。 +4. 将 ANSI 渲染器从 demo 抽为独立模块,补测试(尤其是跨帧、退格、CR/LF)。 +5. 最后再评估是否有必要迁移到 xterm.js(仅在终端兼容性成为瓶颈时)。 + +--- + +## 15. 风险清单(落地前需确认) + +1. 明文密码配置必须替换(令牌化或后端临时凭据)。 +2. 断线错误信息渲染方式应改为安全文本输出(避免 `innerHTML` 注入)。 +3. 若要支持交互式全屏程序(vim/top/tmux),需确认输入模型是按键流而非行输入。 +4. 若继续沿用移动端触控条,需要统一“焦点不丢失”的处理策略(当前两项目实现不同)。 + +--- + +## 16. 快速摘要(一页版) + +1. `xterminal` 是“轻量终端 UI 组件库”;`wetty client` 是“完整 Web SSH 客户端”。 +2. `wetty` 的强项是 xterm.js 终端仿真;`xterminal` 的强项是组件化、轻量、可嵌入。 +3. 两者可融合,但不是无缝替换;最大差异在输入模型、协议耦合、终端仿真深度。 +4. 当前 `wetty` 仓库里 client/server 协议模型存在分叉,后续演进需先统一架构方向。 +