From f01973f9c1f2a3eb56938ea536f574be12d95b84 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 12 Jan 2024 14:03:27 +0100 Subject: [PATCH 01/62] [Task] #6 provide fallback index.html --- httpdocs/index.html | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 httpdocs/index.html diff --git a/httpdocs/index.html b/httpdocs/index.html new file mode 100644 index 00000000..9a20fce2 --- /dev/null +++ b/httpdocs/index.html @@ -0,0 +1,52 @@ + + + + + + Welcome Page + + + +

Welcome

+ + From c19508ff0afd2dc9d8bb73ed69c5ef6d36b07871 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 12 Jan 2024 14:47:11 +0100 Subject: [PATCH 02/62] [Task] #6 production ready code (m) move httpdocs folder to dist have compile without sourcemaps for faster speed --- .github/workflows/build.yml | 2 +- package.json | 5 ++++- tsconfig.prod.json | 6 ++++++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 tsconfig.prod.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 097687ed..57fe368a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: - run: node --version - uses: actions/checkout@v3 - run: npm ci - - run: npm run build --if-present + - run: npm run build:prod --if-present - name: Start server and test server response run: | npm start & diff --git a/package.json b/package.json index c555c50b..7d9bdd96 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "index.js", "type": "module", "scripts": { - "build": "npx tsc", + "build": "npx tsc && cp -R httpdocs/ dist/", + "build:prod": "npx tsc -p ./tsconfig.prod.json && cp -R httpdocs/ dist/", "start": "node dist/app.js", "dev": "nodemon src/app.ts", "lint": "eslint . --fix" @@ -27,3 +28,5 @@ "express": "^4.18.2" } } + + diff --git a/tsconfig.prod.json b/tsconfig.prod.json new file mode 100644 index 00000000..513a96cc --- /dev/null +++ b/tsconfig.prod.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": false + } +} \ No newline at end of file From e09f9789eb032c1220b7fca8af1e9509148cef73 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 12 Jan 2024 15:41:34 +0100 Subject: [PATCH 03/62] [Task] #6 create github action for upload when main is updated (#21) --- .github/workflows/ftp.yml | 25 +++++++++++++++++++++++++ .github/workflows/label.yml | 22 ---------------------- 2 files changed, 25 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/ftp.yml delete mode 100644 .github/workflows/label.yml diff --git a/.github/workflows/ftp.yml b/.github/workflows/ftp.yml new file mode 100644 index 00000000..b379796b --- /dev/null +++ b/.github/workflows/ftp.yml @@ -0,0 +1,25 @@ +name: Deploy via ftp +on: + push: + branches: [ "main" ] +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 20 + - name: Install dependencies + run: npm i + - name: Build + run: npm run build:prod + - name: Upload ftp + uses: GenieTim/ftp-action@v4.0.1 + with: + host: ${{ secrets.FTP_SERVER }} + user: ${{ secrets.FTP_USERNAME }} + password: ${{ secrets.FTP_PASSWORD }} + localDir: "dist" diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml deleted file mode 100644 index 46135690..00000000 --- a/.github/workflows/label.yml +++ /dev/null @@ -1,22 +0,0 @@ -# This workflow will triage pull requests and apply a label based on the -# paths that are modified in the pull request. -# -# To use this workflow, you will need to set up a .github/labeler.yml -# file with configuration. For more information, see: -# https://github.com/actions/labeler - -name: Labeler -on: [pull_request_target] - -jobs: - label: - - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - - steps: - - uses: actions/labeler@v4 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" From 9c2a2fce30d47758629a36c7ac827bb27d2e869f Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 12 Jan 2024 15:57:41 +0100 Subject: [PATCH 04/62] [change] #6 new ftp upload action --- .github/workflows/ftp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ftp.yml b/.github/workflows/ftp.yml index b379796b..01aa3d61 100644 --- a/.github/workflows/ftp.yml +++ b/.github/workflows/ftp.yml @@ -17,9 +17,9 @@ jobs: - name: Build run: npm run build:prod - name: Upload ftp - uses: GenieTim/ftp-action@v4.0.1 + uses: airvzxf/ftp-deployment-action@latest with: host: ${{ secrets.FTP_SERVER }} user: ${{ secrets.FTP_USERNAME }} password: ${{ secrets.FTP_PASSWORD }} - localDir: "dist" + local_dir: "dist" From bae1b4e379595c8696f6d789101e2722e939c8cc Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 12 Jan 2024 16:13:30 +0100 Subject: [PATCH 05/62] [Fix] #6 replace host with server in ftp action --- .github/workflows/ftp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ftp.yml b/.github/workflows/ftp.yml index 01aa3d61..eb615d8f 100644 --- a/.github/workflows/ftp.yml +++ b/.github/workflows/ftp.yml @@ -19,7 +19,7 @@ jobs: - name: Upload ftp uses: airvzxf/ftp-deployment-action@latest with: - host: ${{ secrets.FTP_SERVER }} + server: ${{ secrets.FTP_SERVER }} user: ${{ secrets.FTP_USERNAME }} password: ${{ secrets.FTP_PASSWORD }} local_dir: "dist" From 861336bef72cd39a8fed89576ce1f44f84e10f93 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 12 Jan 2024 17:14:37 +0100 Subject: [PATCH 06/62] [Task] #6 basic log (#26) --- package.json | 5 +++-- src/app.ts | 14 +++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7d9bdd96..c485f044 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,11 @@ "main": "index.js", "type": "module", "scripts": { - "build": "npx tsc && cp -R httpdocs/ dist/", + "clean": "rm -rf dist/*", + "build": "rm -rf dist/* && npx tsc && cp -R httpdocs/ dist/", "build:prod": "npx tsc -p ./tsconfig.prod.json && cp -R httpdocs/ dist/", "start": "node dist/app.js", - "dev": "nodemon src/app.ts", + "dev": "rm -rf dist/* && cp -R httpdocs/ dist/ && nodemon src/app.ts", "lint": "eslint . --fix" }, "keywords": [], diff --git a/src/app.ts b/src/app.ts index 9aafa271..a2daf093 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,14 +1,22 @@ import express from 'express'; import { Request, Response } from 'express'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); const app = express(); const port = 80; app.get('/', (req: Request, res: Response) => { - res.send('Hello World, via TypeScript and Node.js!'); - + res.send('Hello World, via TypeScript and Node.js!'); }); app.listen(port, () => { - console.log(`Server running at http://localhost:${port}`); + const date = new Date().toLocaleString('de-DE', { hour12: false }); + const logPath = join(__dirname, 'httpdocs', 'log.txt'); + fs.appendFileSync(logPath, `Express: Server: ${date} \n`); + console.log(`Server läuft unter http://localhost:${port}`); }); \ No newline at end of file From 63b352543670021a7ba02e47fd0b0853c663e8e1 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 12 Jan 2024 18:32:29 +0100 Subject: [PATCH 07/62] [CHANGE] #6 revert back to require output for production --- package.json | 1 - src/app.ts | 11 ++++------- tsconfig.json | 12 ++++++------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index c485f044..166a51b9 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "version": "0.0.1", "description": "Leaflet Osmand React ExpressJS Coordinates (X)\r > Remember an \"X\" marks the spot.", "main": "index.js", - "type": "module", "scripts": { "clean": "rm -rf dist/*", "build": "rm -rf dist/* && npx tsc && cp -R httpdocs/ dist/", diff --git a/src/app.ts b/src/app.ts index a2daf093..9537cc7b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,11 +1,7 @@ import express from 'express'; import { Request, Response } from 'express'; import fs from 'fs'; -import { fileURLToPath } from 'url'; -import { dirname, join } from 'path'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); +import path from 'path'; const app = express(); const port = 80; @@ -16,7 +12,8 @@ app.get('/', (req: Request, res: Response) => { app.listen(port, () => { const date = new Date().toLocaleString('de-DE', { hour12: false }); - const logPath = join(__dirname, 'httpdocs', 'log.txt'); + const logPath = path.join(__dirname, 'httpdocs', 'log.txt'); fs.appendFileSync(logPath, `Express: Server: ${date} \n`); + console.log(`Server läuft unter http://localhost:${port}`); -}); \ No newline at end of file +}); diff --git a/tsconfig.json b/tsconfig.json index 7a568e39..19cde211 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,11 +3,11 @@ "include": ["src/**/*"], "exclude": ["node_modules"], "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "target": "ES2022", - "sourceMap": true + "rootDir": "src", + "outDir": "dist", + "module": "CommonJS", + "moduleResolution": "node", + "target": "ES6", + "sourceMap": true } } \ No newline at end of file From 95aec15a0eb212ea2b3a6bb351f1dfc8802b5022 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Sun, 14 Jan 2024 22:05:33 +0100 Subject: [PATCH 08/62] [Task] #6 add ability to manually upload to prod --- .github/workflows/ftp.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ftp.yml b/.github/workflows/ftp.yml index eb615d8f..7d206528 100644 --- a/.github/workflows/ftp.yml +++ b/.github/workflows/ftp.yml @@ -1,5 +1,6 @@ name: Deploy via ftp on: + workflow_dispatch: push: branches: [ "main" ] jobs: From 81cac9222d92b6ac5ad8869b0517313732b7bfff Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 19 Jan 2024 16:34:18 +0100 Subject: [PATCH 09/62] [Task] #9 enable manual start of codechecks --- .github/workflows/codeql.yml | 1 + .github/workflows/njsscan.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 09665bdb..dacf974a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -12,6 +12,7 @@ name: "CodeQL" on: + workflow_dispatch: push: branches: [ "dev" ] pull_request: diff --git a/.github/workflows/njsscan.yml b/.github/workflows/njsscan.yml index 77d882c2..5295ead0 100644 --- a/.github/workflows/njsscan.yml +++ b/.github/workflows/njsscan.yml @@ -9,6 +9,7 @@ name: njsscan sarif on: + workflow_dispatch: push: branches: [ "dev" ] pull_request: From 3e1fbbd57e29440fd61c9945b9ee9c562626522b Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 24 Jan 2024 15:09:24 +0100 Subject: [PATCH 10/62] 10 webhook for writing (#36) * [Change] #3 clean up npm scripts, to have clean folder before build * [Task] #10 created data types in typescript * [Temp] #10 created subroute for writing, and folder structure * [Change] #3 include to use relative paths from src folder in ts and node https://stackoverflow.com/questions/43281741/how-can-i-use-paths-in-tsconfig-json See comment from Remo H. Hansen with at least 100 upvoted * [Change] Update VSCode to keep files open * [Task] #18 setup dotenv for secret variables * [Temp, Task] #10 Validate inputs using express-validator and custom functions * [Task] #18 prevent parameter pollution * [Task] #10 validating incoming parameter and logging errors * [Task] #7 add basic cache to express * [Changes] #7 Error Handling, to include basic custom Error Handling * [Task] #10 enhanced validation to only allow known parameters * [Change] #35 added Jest, tests for helper functions when writing * [Task] #10 better error Handling * [Task] #35 add tests for writing webhook validation * [TASK] #18 protect Webhook using KEY * [Fix] #35 test know import path structure now * [Task] #35 add test for protected webhook * [Task] #35 refactor build to run jest tests * [Task] #10 switched to crypto instead of bcrypt for dependency issue see synk inflight * [Fix] #36 PRQ Feedback --- .eslintrc.json | 20 +- .github/workflows/build.yml | 9 +- .vscode/settings.json | 4 + jest.config.js | 9 + package-lock.json | 6004 +++++++++++++++++++++++++++------- package.json | 26 +- src/app.test.ts | 15 + src/app.ts | 37 +- src/cache.ts | 15 + src/controller/write.test.ts | 100 + src/controller/write.ts | 37 + src/error.ts | 32 + src/models/entry.test.ts | 47 + src/models/entry.ts | 91 + src/scripts/crypt.ts | 9 + src/scripts/logger.ts | 17 + src/scripts/time.ts | 0 tsconfig.json | 11 +- types.d.ts | 98 + 19 files changed, 5339 insertions(+), 1242 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 jest.config.js create mode 100644 src/app.test.ts create mode 100644 src/cache.ts create mode 100644 src/controller/write.test.ts create mode 100644 src/controller/write.ts create mode 100644 src/error.ts create mode 100644 src/models/entry.test.ts create mode 100644 src/models/entry.ts create mode 100644 src/scripts/crypt.ts create mode 100644 src/scripts/logger.ts create mode 100644 src/scripts/time.ts create mode 100644 types.d.ts diff --git a/.eslintrc.json b/.eslintrc.json index 1f419d88..30be6b63 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,14 +6,22 @@ "sourceType": "module", "project": "tsconfig.json" }, - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:jest/recommended" + ], "env": { - "node": true + "node": true, + "jest/globals": true }, + "plugins": ["jest", "@typescript-eslint"], "rules": { - //'no-console': 'off', - //'import/prefer-default-export': 'off', - //'@typescript-eslint/no-unused-vars': 'warn', + //"no-console": "off", + //"import/prefer-default-export": "off", + //"@typescript-eslint/no-unused-vars": "warn" + "jest/no-conditional-expect": "off" }, - "ignorePatterns": ["dist"] + "ignorePatterns": ["dist", "jest.config.js"] + } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 57fe368a..4d0f1e0c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,11 +18,8 @@ jobs: - uses: actions/checkout@v3 - run: npm ci - run: npm run build:prod --if-present - - name: Start server and test server response + - name: Start server run: | npm start & - sleep 5 # Wait for server to start - curl localhost:80 - if [ -n "$(jobs -p)" ]; then - kill $(jobs -p) # Kill background jobs - fi + sleep 5 # Give server some time to start + - run: npm test diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..700d6266 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "workbench.editor.enablePreview": false, + "editor.rename.enablePreview": false +} \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..2adbbae4 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,9 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + modulePathIgnorePatterns: ['/dist/'], + moduleNameMapper: { + '^@src/(.*)$': '/src/$1', + }, +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ea8f2847..808a9184 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,16 +8,28 @@ "name": "lorex", "version": "0.0.1", "dependencies": { - "express": "^4.18.2" + "express": "^4.18.2", + "express-validator": "^7.0.1", + "hpp": "^0.2.3", + "module-alias": "^2.2.3" }, "devDependencies": { + "@jest/globals": "^29.7.0", "@tsconfig/node20": "^20.1.2", + "@types/bcrypt": "^5.0.2", "@types/express": "^4.17.21", + "@types/hpp": "^0.2.5", + "@types/jest": "^29.5.11", "@types/node": "^20.10.6", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", + "axios": "^1.6.5", + "dotenv": "^16.3.1", "eslint": "^8.56.0", + "eslint-plugin-jest": "^27.6.3", + "jest": "^29.7.0", "nodemon": "^3.0.2", + "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.3.3" } @@ -31,66 +43,122 @@ "node": ">=0.10.0" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { - "node": ">=12" + "node": ">=6.0.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=4" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", + "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@eslint/eslintrc/node_modules/debug": { + "node_modules/@babel/core/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", @@ -107,503 +175,514 @@ } } }, - "node_modules/@eslint/eslintrc/node_modules/ms": { + "node_modules/@babel/core/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" }, "engines": { - "node": ">=10.10.0" + "node": ">=6.9.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, "dependencies": { - "ms": "2.1.2" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=6.9.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@babel/types": "^7.22.15" }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@babel/types": "^7.22.5" }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@tsconfig/node20": { - "version": "20.1.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.2.tgz", - "integrity": "sha512-madaWq2k+LYMEhmcp0fs+OGaLFk0OenpHa4gmI4VEmCKX4PJntQ6fnnGADVFrVkBj0wIdAlQnK/MrlYTHsa1gQ==", - "dev": true + "node_modules/@babel/helpers": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.8.tgz", + "integrity": "sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + }, + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "dependencies": { - "@types/node": "*" + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.41", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", - "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "color-name": "1.1.3" } }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true + "node_modules/@babel/parser": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/@types/node": { - "version": "20.10.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz", - "integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@types/qs": { - "version": "6.9.11", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", - "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", - "dev": true - }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, "dependencies": { - "@types/mime": "^1", - "@types/node": "*" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@types/serve-static": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", - "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "dependencies": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz", - "integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/type-utils": "6.18.1", - "@typescript-eslint/utils": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@typescript-eslint/parser": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz", - "integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/typescript-estree": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", - "debug": "^4.3.4" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=6.9.0" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@typescript-eslint/parser/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "@babel/helper-plugin-utils": "^7.10.4" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@typescript-eslint/parser/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", - "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz", - "integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.18.1", - "@typescript-eslint/utils": "6.18.1", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": "^16.0.0 || >=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "dependencies": { - "ms": "2.1.2" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=6.0" + "node": ">=6.9.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@typescript-eslint/types": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", - "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">=6.9.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", - "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=6.9.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@babel/traverse": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0" + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "node_modules/@babel/traverse/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", @@ -620,513 +699,2147 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=4" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { + "node_modules/@babel/traverse/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/@typescript-eslint/utils": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz", - "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/typescript-estree": "6.18.1", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", - "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", + "node_modules/@babel/types": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.1", - "eslint-visitor-keys": "^3.4.1" + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=6.9.0" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">= 0.6" + "node": ">=12" } }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": ">=0.4.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", - "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">=0.4.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "ms": "2.1.2" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=10.10.0" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "ms": "2.1.2" }, "engines": { - "node": ">= 8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "node_modules/@humanwhocodes/config-array/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/argparse": { + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "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.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "p-locate": "^4.1.0" }, "engines": { "node": ">=8" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "dependencies": { - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/chalk/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, "engines": { "node": ">=8" } }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "engines": { - "node": ">= 8.10.0" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, "dependencies": { - "safe-buffer": "5.2.1" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" }, "engines": { - "node": ">= 8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, "dependencies": { - "ms": "2.0.0" + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "node_modules/@jest/reporters/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, - "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { - "node": ">= 0.4" + "node": ">=10" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, "engines": { - "node": ">= 0.8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/@jest/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, - "engines": { - "node": ">=0.3.1" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "dependencies": { - "path-type": "^4.0.0" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "dependencies": { - "esutils": "^2.0.2" + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=6.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@tsconfig/node20": { + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.2.tgz", + "integrity": "sha512-madaWq2k+LYMEhmcp0fs+OGaLFk0OenpHa4gmI4VEmCKX4PJntQ6fnnGADVFrVkBj0wIdAlQnK/MrlYTHsa1gQ==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hpp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.5.tgz", + "integrity": "sha512-CBCYtHX0Gb2+jUs8BkLA4UbI/gBzsg93ffVPrVvrKQmXekcbQkxmwr//ccpHUdgVCD2IOaPsSJTXkCV6yeGaHg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.11", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.11.tgz", + "integrity": "sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.10.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz", + "integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/qs": { + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz", + "integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/type-utils": "6.18.1", + "@typescript-eslint/utils": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz", + "integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", + "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz", + "integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/utils": "6.18.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/types": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", + "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", + "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz", + "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", + "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.18.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/axios": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001579", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", + "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.640", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.640.tgz", + "integrity": "sha512-z/6oZ/Muqk4BaE7P69bXhUhpJbUM9ZJeka43ZwxsDshKtePns4mhBlh8bU5+yrnOnz3fhG82XLzGUXazOmsWnA==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/encodeurl": { "version": "1.0.2", @@ -1136,16 +2849,830 @@ "node": ">= 0.8" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "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.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "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.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "27.6.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.3.tgz", + "integrity": "sha512-+YsJFVH6R+tOiO3gCJon5oqn4KWc+mDq2leudk8mrp8RFubLOo9CVyi3cib4L7XMpxExmkmBZQTPDYVBzgpgOA==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.10.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0", + "eslint": "^7.0.0 || ^8.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-jest/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jest/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-plugin-jest/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-plugin-jest/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.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" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-validator": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.0.1.tgz", + "integrity": "sha512-oB+z9QOzQIE8FnlINqyIFA8eIckahC6qc8KtqLdLJcU3/phVyuhXH3bA4qzcrhme+1RYaCSwrq+TlZ/kAKIARA==", + "dependencies": { + "lodash": "^4.17.21", + "validator": "^13.9.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/escape-string-regexp": { + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, "engines": { "node": ">=10" }, @@ -1153,691 +3680,1009 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", - "dev": true, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "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.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "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.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "get-intrinsic": "^1.1.3" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "get-intrinsic": "^1.2.2" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", "dependencies": { - "ms": "2.1.2" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=6.0" + "node": ">= 0.4" + } + }, + "node_modules/hpp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", + "integrity": "sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==", + "dependencies": { + "lodash": "^4.17.12", + "type-is": "^1.6.12" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "engines": { + "node": ">=0.10.0" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dependencies": { - "is-glob": "^4.0.3" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">=10.13.0" + "node": ">=0.10.0" } }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" } }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "dependencies": { - "estraverse": "^5.1.0" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "estraverse": "^5.2.0" + "hasown": "^2.0.0" }, - "engines": { - "node": ">=4.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">=0.10.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, "engines": { - "node": ">= 0.6" + "node": ">=6" } }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.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" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">= 0.10.0" + "node": ">=0.10.0" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, "engines": { - "node": ">=8.6.0" + "node": ">=0.12.0" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", - "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "dependencies": { - "reusify": "^1.0.4" + "engines": { + "node": ">=8" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, "engines": { "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, "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" + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=8" } }, - "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, "engines": { - "node": ">= 0.6" + "node": ">=10" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, - "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, "dependencies": { - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": "*" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" }, "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, "dependencies": { - "get-intrinsic": "^1.1.3" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, "dependencies": { - "function-bind": "^1.1.2" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">= 4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, "engines": { - "node": ">=0.8.19" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">= 0.10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "dependencies": { - "binary-extensions": "^2.0.0" + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "dependencies": { - "is-extglob": "^2.1.1" + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, "engines": { - "node": ">=0.12.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { "node": ">=8" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "node_modules/js-yaml": { @@ -1852,12 +4697,30 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -1870,6 +4733,18 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -1879,6 +4754,24 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -1892,6 +4785,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -1907,6 +4806,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -1925,12 +4835,36 @@ "node": ">=10" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1944,6 +4878,12 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2004,6 +4944,15 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2016,6 +4965,11 @@ "node": "*" } }, + "node_modules/module-alias": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", + "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==" + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2035,6 +4989,18 @@ "node": ">= 0.6" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, "node_modules/nodemon": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.2.tgz", @@ -2110,6 +5076,18 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -2138,6 +5116,21 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -2185,6 +5178,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2197,6 +5199,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2232,6 +5252,12 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -2243,28 +5269,146 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "engines": { - "node": ">=8.6" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, "engines": { - "node": ">= 0.8.0" + "node": ">= 6" } }, "node_modules/proxy-addr": { @@ -2279,6 +5423,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -2294,6 +5444,22 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -2350,6 +5516,12 @@ "node": ">= 0.8" } }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2362,6 +5534,53 @@ "node": ">=8.10.0" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2371,6 +5590,15 @@ "node": ">=4" } }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -2553,6 +5781,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -2565,6 +5799,12 @@ "node": ">=10" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -2574,6 +5814,52 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2582,6 +5868,33 @@ "node": ">= 0.8" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2594,6 +5907,24 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -2618,12 +5949,53 @@ "node": ">=4" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2668,6 +6040,49 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-jest": { + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -2711,6 +6126,27 @@ } } }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "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" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2723,6 +6159,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -2780,6 +6225,36 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2803,6 +6278,38 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2811,6 +6318,15 @@ "node": ">= 0.8" } }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2826,18 +6342,84 @@ "node": ">= 8" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 166a51b9..43db4ea0 100644 --- a/package.json +++ b/package.json @@ -4,29 +4,43 @@ "description": "Leaflet Osmand React ExpressJS Coordinates (X)\r > Remember an \"X\" marks the spot.", "main": "index.js", "scripts": { - "clean": "rm -rf dist/*", - "build": "rm -rf dist/* && npx tsc && cp -R httpdocs/ dist/", + "prebuild": "rm -rf dist/*", + "build": "npx tsc && cp -R httpdocs/ dist/", "build:prod": "npx tsc -p ./tsconfig.prod.json && cp -R httpdocs/ dist/", "start": "node dist/app.js", "dev": "rm -rf dist/* && cp -R httpdocs/ dist/ && nodemon src/app.ts", - "lint": "eslint . --fix" + "lint": "eslint . --fix", + "test": "jest" }, "keywords": [], "author": "Type-Style", "devDependencies": { + "@jest/globals": "^29.7.0", "@tsconfig/node20": "^20.1.2", + "@types/bcrypt": "^5.0.2", "@types/express": "^4.17.21", + "@types/hpp": "^0.2.5", + "@types/jest": "^29.5.11", "@types/node": "^20.10.6", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", + "axios": "^1.6.5", + "dotenv": "^16.3.1", "eslint": "^8.56.0", + "eslint-plugin-jest": "^27.6.3", + "jest": "^29.7.0", "nodemon": "^3.0.2", + "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.3.3" }, "dependencies": { - "express": "^4.18.2" + "express": "^4.18.2", + "express-validator": "^7.0.1", + "hpp": "^0.2.3", + "module-alias": "^2.2.3" + }, + "_moduleAliases": { + "@src": "dist" } } - - diff --git a/src/app.test.ts b/src/app.test.ts new file mode 100644 index 00000000..0682f2d3 --- /dev/null +++ b/src/app.test.ts @@ -0,0 +1,15 @@ +import axios from 'axios'; + +describe('Server Status', () => { + it('The server is running', async () => { + let serverStatus; + try { + const response = await axios.get('http://localhost'); + serverStatus = response.status; + } catch (error) { + console.error(error); + } + + expect(serverStatus).toBe(200); + }) +}) \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 9537cc7b..382c2550 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,19 +1,34 @@ +require('module-alias/register'); +import { config } from 'dotenv'; import express from 'express'; -import { Request, Response } from 'express'; -import fs from 'fs'; +import hpp from 'hpp'; +import cache from './cache'; +import * as error from "./error"; +import writeRouter from '@src/controller/write'; import path from 'path'; +import logger from '@src/scripts/logger'; + +// configurations +config(); const app = express(); -const port = 80; +app.use(hpp()); +app.use(cache); -app.get('/', (req: Request, res: Response) => { +// routes +app.get('/', (req, res) => { res.send('Hello World, via TypeScript and Node.js!'); }); +app.use('/write', writeRouter); -app.listen(port, () => { - const date = new Date().toLocaleString('de-DE', { hour12: false }); - const logPath = path.join(__dirname, 'httpdocs', 'log.txt'); - fs.appendFileSync(logPath, `Express: Server: ${date} \n`); - - console.log(`Server läuft unter http://localhost:${port}`); -}); +// use httpdocs as static folder +app.use('/', express.static(path.join(__dirname, 'httpdocs'))) + +// error handling +app.use(error.notFound); +app.use(error.handler); + +// init server +app.listen(80, () => { + logger.log(`Server running //localhost:80`); +}); \ No newline at end of file diff --git a/src/cache.ts b/src/cache.ts new file mode 100644 index 00000000..c64ae4a8 --- /dev/null +++ b/src/cache.ts @@ -0,0 +1,15 @@ +import {Request, Response, NextFunction } from 'express'; + +const setCache = function (req: Request, res: Response, next: NextFunction) { + const seconds = 60 * 5; // 5 minuits + + // cache get requests but nothing else + if (req.method == "GET") { + res.set("Cache-control", `public, max-age=${seconds}`); + } else { + res.set("Cache-control", 'no-store'); + } + + next(); +} +export default setCache; \ No newline at end of file diff --git a/src/controller/write.test.ts b/src/controller/write.test.ts new file mode 100644 index 00000000..f447d22d --- /dev/null +++ b/src/controller/write.test.ts @@ -0,0 +1,100 @@ +import axios, { AxiosError } from 'axios'; + +describe('HEAD /write', () => { + it('with all parameters correctly set it should succeed', async () => { + const timestamp = new Date().getTime(); + const response = await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); + expect(response.status).toBe(200); + }); + + it('without key it sends 403', async () => { + try { + const timestamp = new Date().getTime(); + await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0`); + } catch (error) { + const axiosError = error as AxiosError; + expect(axiosError.response!.status).toBe(403); + } + }); + + it('with user length not equal to 2 it sends 422', async () => { + try { + const timestamp = new Date().getTime(); + await axios.head(`http://localhost/write?user=x&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); + } catch (error) { + const axiosError = error as AxiosError; + expect(axiosError.response!.status).toBe(422); + } + }); + + + it('with lat not between -90 and 90 it sends 422', async () => { + try { + const timestamp = new Date().getTime(); + await axios.head(`http://localhost/write?user=xx&lat=91.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); + } catch (error) { + const axiosError = error as AxiosError; + expect(axiosError.response!.status).toBe(422); + } + }); + + it('with lon not between -180 and 180 it sends 422', async () => { + try { + const timestamp = new Date().getTime(); + await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=181.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); + } catch (error) { + const axiosError = error as AxiosError; + expect(axiosError.response!.status).toBe(422); + } + }); + + it('with timestamp to old sends 422', async () => { + try { + const timestamp = new Date().getTime() - 24 * 60 * 60 * 1000 * 2; // two days ago + await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); + } catch (error) { + const axiosError = error as AxiosError; + expect(axiosError.response!.status).toBe(422); + } + }) + + it('with hdop not between 0 and 100 it sends 422', async () => { + try { + const timestamp = new Date().getTime(); + await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); + } catch (error) { + const axiosError = error as AxiosError; + expect(axiosError.response!.status).toBe(422); + } + }); + + it('with altitude not between 0 and 10000 it sends 422', async () => { + try { + const timestamp = new Date().getTime(); + await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test`); + } catch (error) { + const axiosError = error as AxiosError; + expect(axiosError.response!.status).toBe(422); + } + }); + + it('with speed not between 0 and 300 it sends 422', async () => { + try { + const timestamp = new Date().getTime(); + await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test`); + } catch (error) { + const axiosError = error as AxiosError; + expect(axiosError.response!.status).toBe(422); + } + }); + + it('with heading not between 0 and 360 it sends 422', async () => { + try { + const timestamp = new Date().getTime(); + await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test`); + } catch (error) { + const axiosError = error as AxiosError; + expect(axiosError.response!.status).toBe(422); + } + }); +}); diff --git a/src/controller/write.ts b/src/controller/write.ts new file mode 100644 index 00000000..b62fd57b --- /dev/null +++ b/src/controller/write.ts @@ -0,0 +1,37 @@ +import express, { Request, Response, NextFunction } from 'express'; +import { entry } from '@src/models/entry'; +import { validationResult } from 'express-validator'; + +// example call: /write?user=xx&lat=00.000&lon=00.000×tamp=1704063600000&hdop=0.0&altitude=0.000&speed=0.000&heading=000.0 +function errorChecking (req:Request, res:Response, next:NextFunction) { + const errors = validationResult(req); + if (!errors.isEmpty()) { + const errorAsJson = { errors: errors.array()}; + const errorAsString = new Error(JSON.stringify(errorAsJson)); + const hasKeyErrors = errors.array().some(error => error.msg.includes("Key")); + + res.status(hasKeyErrors ? 403 : 422); // send forbidden or unprocessable content + return next(errorAsString); + } + + if (req.method == "HEAD") { + res.status(200).end(); + return; + } + + // Regular Save logic from here + + //entry.create(req, res); + //const test = process.env.TEST; + // res.send(req.query); + +} + + +const router = express.Router(); +router.use(entry.validate); + +router.get('/', errorChecking); +router.head('/', errorChecking); + +export default router; \ No newline at end of file diff --git a/src/error.ts b/src/error.ts new file mode 100644 index 00000000..0b0bc047 --- /dev/null +++ b/src/error.ts @@ -0,0 +1,32 @@ +import { Request, Response, NextFunction } from "express"; + +export function notFound(req: Request, res: Response, next: NextFunction) { + res.status(404); + const error = new Error(`🔍 - Not Found - ${req.originalUrl}`); + next(error); +} + +export function handler(err: Error, req: Request, res: Response, next: NextFunction) { + const statusCode = res.statusCode !== 200 ? res.statusCode : 500; + res.status(statusCode); + + let message; + try { + const jsonMessage = JSON.parse(err.message); + message = jsonMessage; + } catch (e) { + message = err.message; + } + + const responseBody = { + status: statusCode, + name: err.name, + message: message, + stack: process.env.NODE_ENV === "development" ? err.stack : "---" + }; + + //logger.error(responseBody); + res.json(responseBody); + + next(); +} diff --git a/src/models/entry.test.ts b/src/models/entry.test.ts new file mode 100644 index 00000000..bd2c0b4d --- /dev/null +++ b/src/models/entry.test.ts @@ -0,0 +1,47 @@ +import { checkNumber, checkTime } from "./entry"; + + +describe("checkNumber", () => { + it("should throw error if value is not provided", () => { + expect(() => checkNumber(0, 100)("")).toThrow(new Error('is required')); + }); + + it("should throw error if value length is more than 12", () => { + expect(() => checkNumber(0, 100)("1234567890123")).toThrow(new Error('Should have a maximum of 11 digits')); + }); + + it("should throw error if value is not a number", () => { + expect(() => checkNumber(0, 100)("abc")).toThrow(new Error('Value should be between 0 and 100')); + }); + + it("should return true if value is a valid number within range", () => { + expect(checkNumber(0, 100)("50")).toBe(true); + }); +}); + +describe("checkTime", () => { + it("should throw error if value is not a number", () => { + expect(() => checkTime("abc")).toThrow(new Error('Timestamp should be a number')); + }); + + it("should throw error if value is not a valid date", () => { + expect(() => checkTime("99999999999999999999")).toThrow(new Error('Timestamp should represent a valid date')); + }); + + it("should throw error if value is more than 1 day in the past", () => { + const date = new Date(); + date.setDate(date.getDate() - 2); // Set date to 2 days ago + expect(() => checkTime(date.getTime().toString())).toThrow(new Error('Timestamp should represent a date not further from server time than 1 day')); + }); + + it("should throw error if value is more than 1 day in the future", () => { + const date = new Date(); + date.setDate(date.getDate() + 2); // Set date to 2 days in the future + expect(() => checkTime(date.getTime().toString())).toThrow(new Error('Timestamp should represent a date not further from server time than 1 day')); + }); + + it("should return true if value is a valid timestamp within 1 day", () => { + const date = new Date(); + expect(checkTime(date.getTime().toString())).toBe(true); + }); +}); diff --git a/src/models/entry.ts b/src/models/entry.ts new file mode 100644 index 00000000..73b7d29c --- /dev/null +++ b/src/models/entry.ts @@ -0,0 +1,91 @@ +import { Request, Response} from 'express'; +import { checkExact, query } from 'express-validator'; +import { crypt } from '@src/scripts/crypt'; + +export const entry = { + create: (req:Request, res:Response) => { + console.log(req.query); + console.log(res); + }, + validate: [ + query('user').isLength({ min: 2, max: 2 }), + query('lat').custom(checkNumber(-90, 90)), + query('lon').custom(checkNumber(-180, 180)), + query('timestamp').custom(checkTime), + query('hdop').custom(checkNumber(0, 100)), + query('altitude').custom(checkNumber(0, 10000)), + query('speed').custom(checkNumber(0, 300)), + query('heading').custom(checkNumber(0, 360)), + query("key").custom(checkKey), + checkExact() + // INFO: if message or any string gets added remember to escape + ] +} + +export function checkNumber(min:number, max:number) { + return (value:string) => { + if (!value) { + throw new Error('is required'); + } + if (value.length > 12) { + throw new Error('Should have a maximum of 11 digits'); + } + + const number = parseFloat(value); + if (isNaN(number) || number < min || number > max) { + throw new Error(`Value should be between ${min} and ${max}`); + } + return true; + }; +} + +export function checkTime(value:string) { + const timestamp = parseFloat(value); + + // Check if it's a number + if (isNaN(timestamp)) { + throw new Error('Timestamp should be a number'); + } + + // Check if it's a valid date + const date = new Date(timestamp); + if (isNaN(date.getTime())) { + throw new Error('Timestamp should represent a valid date'); + } + + if (process.env.NODE_ENV == "development") { + return true; // dev testing convenience + } + + const now = new Date(); + const difference = now.getTime() - date.getTime(); + const oneDayInMilliseconds = 24 * 60 * 60 * 1000; + if (Math.abs(difference) >= oneDayInMilliseconds) { + throw new Error('Timestamp should represent a date not further from server time than 1 day'); + } + + return true +} + +function checkKey(value:string) { + if (process.env.NODE_ENV != "production" && value == "test") { + return true; // dev testing convenience + } + + if (!value) { + throw new Error('Key required'); + } + + value = decodeURIComponent(value); + + const hash = crypt(value); + + if (process.env.KEYB != hash) { + if (process.env.NODE_ENV == "development") { + console.log(hash); + } + throw new Error('Key does not match'); + } + + return true; +} \ No newline at end of file diff --git a/src/scripts/crypt.ts b/src/scripts/crypt.ts new file mode 100644 index 00000000..b14ef427 --- /dev/null +++ b/src/scripts/crypt.ts @@ -0,0 +1,9 @@ +import * as crypto from 'crypto'; + +export const crypt = function (value:string) { + const key = process.env.KEYA; + if (!key) { + throw new Error('KEYA is not defined in the environment variables'); + } + return crypto.createHmac('sha256', key).update(value).digest("base64"); +}; \ No newline at end of file diff --git a/src/scripts/logger.ts b/src/scripts/logger.ts new file mode 100644 index 00000000..b58f3662 --- /dev/null +++ b/src/scripts/logger.ts @@ -0,0 +1,17 @@ +// primitive text logger +import fs from 'fs'; +import path from 'path'; + +const logPath = path.resolve(__dirname, '../httpdocs', 'log.txt'); +const date = new Date().toLocaleString('de-DE', { hour12: false }); + +export default { + log: (message:string|JSON) => { + fs.appendFileSync(logPath, `${date} \t|\t ${message} \n`); + console.log(message); + }, + error: (message:string|JSON|Response.Error) => { + fs.appendFileSync(logPath, `${date} \t|\t ERROR: ${message} \n`); + console.error(message); + }, +} \ No newline at end of file diff --git a/src/scripts/time.ts b/src/scripts/time.ts new file mode 100644 index 00000000..e69de29b diff --git a/tsconfig.json b/tsconfig.json index 19cde211..613bc082 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,13 @@ "module": "CommonJS", "moduleResolution": "node", "target": "ES6", - "sourceMap": true - } + "sourceMap": true, + "baseUrl": "./src", + "paths": { + "@src/*": ["./*"], + } + }, + "files": [ + "types.d.ts" + ] } \ No newline at end of file diff --git a/types.d.ts b/types.d.ts new file mode 100644 index 00000000..b3d3397f --- /dev/null +++ b/types.d.ts @@ -0,0 +1,98 @@ + +/* eslint-disable @typescript-eslint/no-unused-vars */ + +type NumericRange = + ARR['length'] extends END ? ACC | START | END : + NumericRange; + +namespace Response { + interface Message { + message: string; + data?: string|JSON; + } + + interface Error extends Response.Message { + stack?: string, + name?: string, + status?: number + } +} +namespace Models { + interface IEntry { + /** + * height above ground in meters, as received by gps + */ + altitude: number, + + /** + * Direction in degrees between two coordinate pairs: 0°-360° + */ + angle: NumericRange<0, 360>, + + /** + * object containing horizontal vertical and total distance, in meters + */ + distance: { + horizontal: number, + vertical: number, + total: number + }, + + /** + * object containing horizontal vertical and total speed, in km/h + */ + speeed: { + horizontal: number, + vertical: number, + total: number + }, + + /** + * index, position of the entry point in the chain + */ + index: number, + + /** + * Heading or Bearing as recieved from gps + */ + heading: NumericRange<0, 360>, + + /** + * lat + */ + lat: number, + + + /** + * lon + */ + lon: number, + + /** + * hdop: accuracy as recieved by gps + */ + hdop: number, + + /** + * ignore: defined by hdop and time difference; determines whether the view shall display this entry + */ + ignore: boolean, + + + /** + * time object containing UNIX timestamps with milliseconds, gps creation time (as recieved via gps), server time (when the server recieved and computed it), differce to last entry (time between waypoints), upload time differnce + */ + time: { + created: number, + recieved: number, + uploadDuration: number, + diff: number + createdString: string + }, + + /** + * user as recieved + */ + user: string + } +} From 781d61d7502246353fb1d5eef8983a97e973fabf Mon Sep 17 00:00:00 2001 From: Type-Style Date: Thu, 25 Jan 2024 21:06:17 +0100 Subject: [PATCH 11/62] [Task] #3 improve error handling, logger and added chalk to colorize console output. Had to use chalk version 4 because of typescript converting to require, and chalk5 do want import syntax. --- package-lock.json | 53 ++++++++++++++++++++++++++++++++++++++++++- package.json | 5 +++- src/error.ts | 13 ++++++++++- src/scripts/logger.ts | 18 ++++++++++----- 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 808a9184..e2298f39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,14 @@ "express": "^4.18.2", "express-validator": "^7.0.1", "hpp": "^0.2.3", - "module-alias": "^2.2.3" + "module-alias": "^2.2.3", + "node-fetch": "^2.7.0" }, "devDependencies": { "@jest/globals": "^29.7.0", "@tsconfig/node20": "^20.1.2", "@types/bcrypt": "^5.0.2", + "@types/dotenv": "^8.2.0", "@types/express": "^4.17.21", "@types/hpp": "^0.2.5", "@types/jest": "^29.5.11", @@ -24,6 +26,7 @@ "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", "axios": "^1.6.5", + "chalk": "^4.1.2", "dotenv": "^16.3.1", "eslint": "^8.56.0", "eslint-plugin-jest": "^27.6.3", @@ -1519,6 +1522,16 @@ "@types/node": "*" } }, + "node_modules/@types/dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-ylSC9GhfRH7m1EUXBXofhgx4lUWmFeQDINW5oLuS+gxWdfUeW4zJdeVTYVkexEW+e2VUvlZR2kGnGGipAWR7kw==", + "deprecated": "This is a stub types definition. dotenv provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "dotenv": "*" + } + }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -4989,6 +5002,25 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6028,6 +6060,11 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -6327,6 +6364,20 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 43db4ea0..6aa27dd6 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@jest/globals": "^29.7.0", "@tsconfig/node20": "^20.1.2", "@types/bcrypt": "^5.0.2", + "@types/dotenv": "^8.2.0", "@types/express": "^4.17.21", "@types/hpp": "^0.2.5", "@types/jest": "^29.5.11", @@ -25,6 +26,7 @@ "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", "axios": "^1.6.5", + "chalk": "^4.1.2", "dotenv": "^16.3.1", "eslint": "^8.56.0", "eslint-plugin-jest": "^27.6.3", @@ -38,7 +40,8 @@ "express": "^4.18.2", "express-validator": "^7.0.1", "hpp": "^0.2.3", - "module-alias": "^2.2.3" + "module-alias": "^2.2.3", + "node-fetch": "^2.7.0" }, "_moduleAliases": { "@src": "dist" diff --git a/src/error.ts b/src/error.ts index 0b0bc047..4f11a2f6 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,4 +1,14 @@ import { Request, Response, NextFunction } from "express"; +import logger from '@src/scripts/logger'; + +export function create(res:Response, status:number = 500, message:string, next:NextFunction) { + /** + * takes httpStatusCode and Message and forwards to error Handling + */ + const error = new Error(message); + res.status(status); + return next(error) +} export function notFound(req: Request, res: Response, next: NextFunction) { res.status(404); @@ -7,6 +17,7 @@ export function notFound(req: Request, res: Response, next: NextFunction) { } export function handler(err: Error, req: Request, res: Response, next: NextFunction) { + const statusCode = res.statusCode !== 200 ? res.statusCode : 500; res.status(statusCode); @@ -25,7 +36,7 @@ export function handler(err: Error, req: Request, res: Response stack: process.env.NODE_ENV === "development" ? err.stack : "---" }; - //logger.error(responseBody); + logger.error(responseBody); res.json(responseBody); next(); diff --git a/src/scripts/logger.ts b/src/scripts/logger.ts index b58f3662..06479f77 100644 --- a/src/scripts/logger.ts +++ b/src/scripts/logger.ts @@ -1,17 +1,23 @@ // primitive text logger -import fs from 'fs'; -import path from 'path'; +import fs from 'fs'; // typescript will compile to require +import path from 'path'; // typescript will compile to require +import chalk from "chalk"; // keep import syntax after compile const logPath = path.resolve(__dirname, '../httpdocs', 'log.txt'); const date = new Date().toLocaleString('de-DE', { hour12: false }); export default { - log: (message:string|JSON) => { + log: (message:string|JSON, showDateInConsole:boolean=false, showLogInTest=false) => { fs.appendFileSync(logPath, `${date} \t|\t ${message} \n`); - console.log(message); + if (showDateInConsole) { + message = `${chalk.gray(date + ":")} ${message}`; + } + if (process.env.NODE_ENV == "development" || showLogInTest && process.env.NODE_ENV == "test") { + console.log(message); + } }, error: (message:string|JSON|Response.Error) => { fs.appendFileSync(logPath, `${date} \t|\t ERROR: ${message} \n`); console.error(message); - }, -} \ No newline at end of file + } +} From 6d8463872c8489fc06f4b2c28711d38377a70cd1 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Thu, 25 Jan 2024 21:07:18 +0100 Subject: [PATCH 12/62] [Change] #3 nodemon to clear console when in dev mode --- nodemon.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nodemon.json b/nodemon.json index 92b25d2d..ad7eb013 100644 --- a/nodemon.json +++ b/nodemon.json @@ -2,5 +2,8 @@ "watch": ["src"], "ext": ".ts", "ignore": [], - "exec": "tsc && node dist/app.js" + "exec": "tsc && node dist/app.js", + "events": { + "start": "clear" + } } \ No newline at end of file From 63085ed80aa33d6dd007492a5a24b025812f7ff8 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Thu, 25 Jan 2024 21:08:04 +0100 Subject: [PATCH 13/62] [!Task] #32 webhook creates folder and file based on date --- .gitignore | 2 ++ src/app.ts | 3 +-- src/controller/write.ts | 20 ++++++++-------- src/models/entry.ts | 12 ++++++---- src/scripts/file.ts | 51 +++++++++++++++++++++++++++++++++++++++++ types.d.ts | 23 ++++++++++++------- 6 files changed, 88 insertions(+), 23 deletions(-) create mode 100644 src/scripts/file.ts diff --git a/.gitignore b/.gitignore index c6bba591..c53815fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +data/ + # Logs logs *.log diff --git a/src/app.ts b/src/app.ts index 382c2550..8b3fd8d8 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,7 +8,6 @@ import writeRouter from '@src/controller/write'; import path from 'path'; import logger from '@src/scripts/logger'; - // configurations config(); const app = express(); @@ -30,5 +29,5 @@ app.use(error.handler); // init server app.listen(80, () => { - logger.log(`Server running //localhost:80`); + logger.log(`Server running //localhost:80`, true); }); \ No newline at end of file diff --git a/src/controller/write.ts b/src/controller/write.ts index b62fd57b..4bf9a095 100644 --- a/src/controller/write.ts +++ b/src/controller/write.ts @@ -1,17 +1,19 @@ import express, { Request, Response, NextFunction } from 'express'; import { entry } from '@src/models/entry'; import { validationResult } from 'express-validator'; +import { create as createError } from '@src/error'; + // example call: /write?user=xx&lat=00.000&lon=00.000×tamp=1704063600000&hdop=0.0&altitude=0.000&speed=0.000&heading=000.0 function errorChecking (req:Request, res:Response, next:NextFunction) { const errors = validationResult(req); if (!errors.isEmpty()) { const errorAsJson = { errors: errors.array()}; - const errorAsString = new Error(JSON.stringify(errorAsJson)); + const errorAsString = JSON.stringify(errorAsJson); const hasKeyErrors = errors.array().some(error => error.msg.includes("Key")); - res.status(hasKeyErrors ? 403 : 422); // send forbidden or unprocessable content - return next(errorAsString); + // send forbidden or unprocessable content + return createError(res, hasKeyErrors ? 403 : 422, errorAsString, next) } if (req.method == "HEAD") { @@ -19,12 +21,12 @@ function errorChecking (req:Request, res:Response, next:NextFunction) { return; } - // Regular Save logic from here - - //entry.create(req, res); - //const test = process.env.TEST; - // res.send(req.query); - + // Regular Save logic from here + entry.create(req, res, next); + + + + res.send(req.query); } diff --git a/src/models/entry.ts b/src/models/entry.ts index 73b7d29c..5f9088f7 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -1,11 +1,15 @@ -import { Request, Response} from 'express'; +import { NextFunction, Request, Response} from 'express'; import { checkExact, query } from 'express-validator'; import { crypt } from '@src/scripts/crypt'; +import * as file from '@src/scripts/file'; + export const entry = { - create: (req:Request, res:Response) => { - console.log(req.query); - console.log(res); + create: async (req:Request, res:Response, next:NextFunction) => { + const fileObj:File.Obj= file.createFile(res, next); + fileObj.content = await file.readAsJson(res, fileObj.path, next); + + console.log(fileObj.content); }, validate: [ query('user').isLength({ min: 2, max: 2 }), diff --git a/src/scripts/file.ts b/src/scripts/file.ts new file mode 100644 index 00000000..a7ba1927 --- /dev/null +++ b/src/scripts/file.ts @@ -0,0 +1,51 @@ +import fs from 'fs'; +import path from 'path'; +import { promisify } from 'util'; +import { create as createError } from '@src/error'; +import { NextFunction, Response } from 'express'; +import logger from '@src/scripts/logger'; + +export const createFile = (res: Response, next: NextFunction): File.Obj => { + const date = new Date(); + const formattedDate = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + const dirPath = path.resolve(__dirname, '../data'); + const filePath = path.resolve(dirPath, `data-${formattedDate}.json`); + + if (!fs.existsSync(dirPath)){ + fs.mkdirSync(dirPath, { recursive: true }); + logger.log("data folder did not exist, but created now"); + } + + let fileExisted = true; + if (!fs.existsSync(filePath)) { // check if file exist + fileExisted = false; + try { + // fs.appendFileSync(filePath, 'test'); + fs.writeFileSync(filePath, ''); + } catch (err) { + createError(res, 500, "File cannot be written to", next); + } + } + + return { path: filePath, content: fileExisted ? undefined : '' }; // if the file did not exist before, the content is emptyString +}; + + + + +const readFileAsync = promisify(fs.readFile); + +export async function readAsJson(res: Response, filePath: string, next: NextFunction): Promise { + const data = await readFileAsync(filePath, 'utf-8'); + console.log(data); + + if (data === '') { + return ''; + } + try { + return JSON.parse(data); + } catch (err) { + createError(res, 500, "File contains wrong content", next); + return undefined; + } +} diff --git a/types.d.ts b/types.d.ts index b3d3397f..97039812 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,14 +1,14 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -type NumericRange = - ARR['length'] extends END ? ACC | START | END : - NumericRange; +type NumericRange = + ARR['length'] extends END ? ACC | START | END : + NumericRange; namespace Response { interface Message { message: string; - data?: string|JSON; + data?: string | JSON; } interface Error extends Response.Message { @@ -17,6 +17,13 @@ namespace Response { status?: number } } +namespace File { + interface Obj { + path: string + content?: JSON | '' + } +} + namespace Models { interface IEntry { /** @@ -33,9 +40,9 @@ namespace Models { * object containing horizontal vertical and total distance, in meters */ distance: { - horizontal: number, - vertical: number, - total: number + horizontal: number, + vertical: number, + total: number }, /** @@ -61,7 +68,7 @@ namespace Models { * lat */ lat: number, - + /** * lon From 8873654b0394929394400f62b4eb16514997ee98 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 26 Jan 2024 16:46:25 +0100 Subject: [PATCH 14/62] [Change] #35 relocated tests and refactor write, also added file check --- src/controller/write.test.ts | 100 ---------------------------- src/controller/write.ts | 2 - src/scripts/file.ts | 1 - src/{ => tests}/app.test.ts | 2 +- src/{models => tests}/entry.test.ts | 2 +- src/tests/write.test.ts | 87 ++++++++++++++++++++++++ 6 files changed, 89 insertions(+), 105 deletions(-) delete mode 100644 src/controller/write.test.ts rename src/{ => tests}/app.test.ts (81%) rename src/{models => tests}/entry.test.ts (97%) create mode 100644 src/tests/write.test.ts diff --git a/src/controller/write.test.ts b/src/controller/write.test.ts deleted file mode 100644 index f447d22d..00000000 --- a/src/controller/write.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import axios, { AxiosError } from 'axios'; - -describe('HEAD /write', () => { - it('with all parameters correctly set it should succeed', async () => { - const timestamp = new Date().getTime(); - const response = await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - expect(response.status).toBe(200); - }); - - it('without key it sends 403', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(403); - } - }); - - it('with user length not equal to 2 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=x&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - - it('with lat not between -90 and 90 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=91.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with lon not between -180 and 180 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=181.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with timestamp to old sends 422', async () => { - try { - const timestamp = new Date().getTime() - 24 * 60 * 60 * 1000 * 2; // two days ago - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }) - - it('with hdop not between 0 and 100 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with altitude not between 0 and 10000 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with speed not between 0 and 300 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with heading not between 0 and 360 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); -}); diff --git a/src/controller/write.ts b/src/controller/write.ts index 4bf9a095..4b61d302 100644 --- a/src/controller/write.ts +++ b/src/controller/write.ts @@ -24,8 +24,6 @@ function errorChecking (req:Request, res:Response, next:NextFunction) { // Regular Save logic from here entry.create(req, res, next); - - res.send(req.query); } diff --git a/src/scripts/file.ts b/src/scripts/file.ts index a7ba1927..75040a0e 100644 --- a/src/scripts/file.ts +++ b/src/scripts/file.ts @@ -20,7 +20,6 @@ export const createFile = (res: Response, next: NextFunction): File.Obj => { if (!fs.existsSync(filePath)) { // check if file exist fileExisted = false; try { - // fs.appendFileSync(filePath, 'test'); fs.writeFileSync(filePath, ''); } catch (err) { createError(res, 500, "File cannot be written to", next); diff --git a/src/app.test.ts b/src/tests/app.test.ts similarity index 81% rename from src/app.test.ts rename to src/tests/app.test.ts index 0682f2d3..453caf40 100644 --- a/src/app.test.ts +++ b/src/tests/app.test.ts @@ -4,7 +4,7 @@ describe('Server Status', () => { it('The server is running', async () => { let serverStatus; try { - const response = await axios.get('http://localhost'); + const response = await axios.get('http://localhost:80'); serverStatus = response.status; } catch (error) { console.error(error); diff --git a/src/models/entry.test.ts b/src/tests/entry.test.ts similarity index 97% rename from src/models/entry.test.ts rename to src/tests/entry.test.ts index bd2c0b4d..48058e7f 100644 --- a/src/models/entry.test.ts +++ b/src/tests/entry.test.ts @@ -1,4 +1,4 @@ -import { checkNumber, checkTime } from "./entry"; +import { checkNumber, checkTime } from "../models/entry"; describe("checkNumber", () => { diff --git a/src/tests/write.test.ts b/src/tests/write.test.ts new file mode 100644 index 00000000..2a61ef8e --- /dev/null +++ b/src/tests/write.test.ts @@ -0,0 +1,87 @@ +import axios, { AxiosError } from 'axios'; +import fs from "fs"; +import path from "path"; + +async function callServer(timestamp = new Date().getTime(), query: string, expectStatus: number = 200, method:string = "HEAD") { + const url = new URL("http://localhost:80/write?"); + url.search = "?" + query; + const params = new URLSearchParams(url.search); + params.set("timestamp", timestamp.toString()); + url.search = params.toString(); + + let response; + if (expectStatus == 200) { + if (method == "GET") { + response = await axios.get(url.toString()); + } else { + response = await axios.head(url.toString()); + } + expect(response.status).toBe(expectStatus); + } else { + try { + await axios.head(url.toString()); + } catch (error) { + const axiosError = error as AxiosError; + expect(axiosError.response!.status).toBe(expectStatus); + } + } +} + +describe('HEAD /write', () => { + it('with all parameters correctly set it should succeed', async () => { + callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200); + }); + + it('without key it sends 403', async () => { + callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0", 403); + }); + + it('with user length not equal to 2 it sends 422', async () => { + callServer(undefined, "user=x&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); + + it('with lat not between -90 and 90 it sends 422', async () => { + callServer(undefined, "user=xx&lat=91.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); + + it('with lon not between -180 and 180 it sends 422', async () => { + callServer(undefined, "user=xx&lat=45.000&lon=181.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); + + it('with timestamp to old sends 422', async () => { + const timestamp = new Date().getTime() - 24 * 60 * 60 * 1000 * 2; // two days ago + callServer(timestamp, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }) + + it('with hdop not between 0 and 100 it sends 422', async () => { + callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); + + it('with altitude not between 0 and 10000 it sends 422', async () => { + callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test", 422); + }); + + it('with speed not between 0 and 300 it sends 422', async () => { + callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test", 422); + }); + + it('with heading not between 0 and 360 it sends 422', async () => { + callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test", 422); + }); +}); + + +describe("GET /write", () => { + it('there should a file of the current date', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + + const date = new Date(); + const formattedDate = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + const dirPath = path.resolve(__dirname, '../../dist/data/'); + const filePath = path.resolve(dirPath, `data-${formattedDate}.json`); + console.log(filePath); + fs.access(filePath, fs.constants.F_OK, (err) => { + expect(err).toBeFalsy(); + }); + }); +}); \ No newline at end of file From 91fba3bf1722ff232fa31d3787151ae129b6a4fe Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 26 Jan 2024 17:14:54 +0100 Subject: [PATCH 15/62] [Task] #18, installed helmet, configured self as CSP origin --- package-lock.json | 9 +++++++++ package.json | 1 + src/app.ts | 14 ++++++++++++++ src/scripts/file.ts | 28 +++++++++++++--------------- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index e2298f39..6632d1b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "express": "^4.18.2", "express-validator": "^7.0.1", + "helmet": "^7.1.0", "hpp": "^0.2.3", "module-alias": "^2.2.3", "node-fetch": "^2.7.0" @@ -3769,6 +3770,14 @@ "node": ">= 0.4" } }, + "node_modules/helmet": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz", + "integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/hpp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", diff --git a/package.json b/package.json index 6aa27dd6..c5fd1532 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "dependencies": { "express": "^4.18.2", "express-validator": "^7.0.1", + "helmet": "^7.1.0", "hpp": "^0.2.3", "module-alias": "^2.2.3", "node-fetch": "^2.7.0" diff --git a/src/app.ts b/src/app.ts index 8b3fd8d8..fd0f1eac 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,6 +1,7 @@ require('module-alias/register'); import { config } from 'dotenv'; import express from 'express'; +import helmet from 'helmet'; import hpp from 'hpp'; import cache from './cache'; import * as error from "./error"; @@ -11,6 +12,19 @@ import logger from '@src/scripts/logger'; // configurations config(); const app = express(); +app.use( + helmet({ + contentSecurityPolicy: { + directives: { + "default-src": "self", + "script-src": "self", + "img-src": "*", + "media-src": "self" + }, + }, + }), +); + app.use(hpp()); app.use(cache); diff --git a/src/scripts/file.ts b/src/scripts/file.ts index 75040a0e..9660fcf2 100644 --- a/src/scripts/file.ts +++ b/src/scripts/file.ts @@ -11,19 +11,19 @@ export const createFile = (res: Response, next: NextFunction): File.Obj => { const dirPath = path.resolve(__dirname, '../data'); const filePath = path.resolve(dirPath, `data-${formattedDate}.json`); - if (!fs.existsSync(dirPath)){ - fs.mkdirSync(dirPath, { recursive: true }); - logger.log("data folder did not exist, but created now"); + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + logger.log("data folder did not exist, but created now"); } let fileExisted = true; if (!fs.existsSync(filePath)) { // check if file exist - fileExisted = false; - try { - fs.writeFileSync(filePath, ''); - } catch (err) { - createError(res, 500, "File cannot be written to", next); - } + fileExisted = false; + try { + fs.writeFileSync(filePath, ''); + } catch (err) { + createError(res, 500, "File cannot be written to", next); + } } return { path: filePath, content: fileExisted ? undefined : '' }; // if the file did not exist before, the content is emptyString @@ -36,14 +36,12 @@ const readFileAsync = promisify(fs.readFile); export async function readAsJson(res: Response, filePath: string, next: NextFunction): Promise { const data = await readFileAsync(filePath, 'utf-8'); - console.log(data); - if (data === '') { - return ''; - } - try { + if (data === '') { return ''; } + + try { return JSON.parse(data); - } catch (err) { + } catch (err) { createError(res, 500, "File contains wrong content", next); return undefined; } From a22f970e2c739337da56b875e942dc07e0f3df8a Mon Sep 17 00:00:00 2001 From: Type-Style Date: Mon, 29 Jan 2024 08:44:55 +0100 Subject: [PATCH 16/62] [Fix] moved chalk out of dev dependency --- package-lock.json | 51 +++---------------------------------------- package.json | 5 ++--- src/scripts/logger.ts | 2 +- 3 files changed, 6 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6632d1b3..4c0b3553 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,12 @@ "name": "lorex", "version": "0.0.1", "dependencies": { + "chalk": "^4.1.2", "express": "^4.18.2", "express-validator": "^7.0.1", "helmet": "^7.1.0", "hpp": "^0.2.3", - "module-alias": "^2.2.3", - "node-fetch": "^2.7.0" + "module-alias": "^2.2.3" }, "devDependencies": { "@jest/globals": "^29.7.0", @@ -27,7 +27,6 @@ "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", "axios": "^1.6.5", - "chalk": "^4.1.2", "dotenv": "^16.3.1", "eslint": "^8.56.0", "eslint-plugin-jest": "^27.6.3", @@ -2112,7 +2111,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2452,7 +2450,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2468,7 +2465,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2477,7 +2473,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -2576,7 +2571,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2587,8 +2581,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -5011,25 +5004,6 @@ "node": ">= 0.6" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6069,11 +6043,6 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -6373,20 +6342,6 @@ "makeerror": "1.0.12" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index c5fd1532..7e450e91 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", "axios": "^1.6.5", - "chalk": "^4.1.2", "dotenv": "^16.3.1", "eslint": "^8.56.0", "eslint-plugin-jest": "^27.6.3", @@ -37,12 +36,12 @@ "typescript": "^5.3.3" }, "dependencies": { + "chalk": "^4.1.2", "express": "^4.18.2", "express-validator": "^7.0.1", "helmet": "^7.1.0", "hpp": "^0.2.3", - "module-alias": "^2.2.3", - "node-fetch": "^2.7.0" + "module-alias": "^2.2.3" }, "_moduleAliases": { "@src": "dist" diff --git a/src/scripts/logger.ts b/src/scripts/logger.ts index 06479f77..9cbf34b7 100644 --- a/src/scripts/logger.ts +++ b/src/scripts/logger.ts @@ -10,7 +10,7 @@ export default { log: (message:string|JSON, showDateInConsole:boolean=false, showLogInTest=false) => { fs.appendFileSync(logPath, `${date} \t|\t ${message} \n`); if (showDateInConsole) { - message = `${chalk.gray(date + ":")} ${message}`; + message = `${chalk.dim(date + ":")} ${message}`; } if (process.env.NODE_ENV == "development" || showLogInTest && process.env.NODE_ENV == "test") { console.log(message); From c779de643a605cf4c7f37a9d83b200d8556053f8 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Mon, 29 Jan 2024 09:06:06 +0100 Subject: [PATCH 17/62] [Task] #32 error logging and text output improvement, log string instead of "object" --- src/scripts/logger.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/logger.ts b/src/scripts/logger.ts index 9cbf34b7..0beeaea2 100644 --- a/src/scripts/logger.ts +++ b/src/scripts/logger.ts @@ -8,6 +8,7 @@ const date = new Date().toLocaleString('de-DE', { hour12: false }); export default { log: (message:string|JSON, showDateInConsole:boolean=false, showLogInTest=false) => { + message = JSON.stringify(message); fs.appendFileSync(logPath, `${date} \t|\t ${message} \n`); if (showDateInConsole) { message = `${chalk.dim(date + ":")} ${message}`; From 8a5137d0578259a4793a1bb91af452a36b394edb Mon Sep 17 00:00:00 2001 From: Type-Style Date: Mon, 29 Jan 2024 09:09:19 +0100 Subject: [PATCH 18/62] [Task] #18 CSP Update to allow localhost for testing --- src/app.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app.ts b/src/app.ts index fd0f1eac..d65799a3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -16,10 +16,8 @@ app.use( helmet({ contentSecurityPolicy: { directives: { - "default-src": "self", - "script-src": "self", - "img-src": "*", - "media-src": "self" + "default-src": "'self'", + "img-src": "*" }, }, }), @@ -43,5 +41,5 @@ app.use(error.handler); // init server app.listen(80, () => { - logger.log(`Server running //localhost:80`, true); + logger.log(`Server running //localhost:80, ENV: ${process.env.NODE_ENV}`, true); }); \ No newline at end of file From 3e8dab11a20a5bf1d5c6dfa60ee01d6c87d7520c Mon Sep 17 00:00:00 2001 From: Type-Style Date: Mon, 29 Jan 2024 18:40:15 +0100 Subject: [PATCH 19/62] [Fix] #3 debugging setup improvments --- .vscode/launch.json | 32 +++++++++++++------------------- .vscode/tasks.json | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 19 deletions(-) create mode 100644 .vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json index c56ec224..8425a2bc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,21 +1,15 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Debug server.ts", - "skipFiles": [ - "/**" - ], - "program": "${workspaceFolder}\\src\\app.ts", - "preLaunchTask": "tsc: build - tsconfig.json", - "outFiles": [ - "${workspaceFolder}/dist/**/*.js" - ] - } - ] + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug TypeScript", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}\\src\\app.ts", + "preLaunchTask": "build" + } + ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..864577f6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,18 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "npx tsc -p tsconfig.json", + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "silent" + }, + "problemMatcher": "$tsc" + } + ] +} From ff7c28736a560c16cc45a24a663052879b7a12ef Mon Sep 17 00:00:00 2001 From: Type-Style Date: Mon, 29 Jan 2024 18:41:11 +0100 Subject: [PATCH 20/62] [FIX] #10 Error Handling --- src/controller/write.ts | 10 +++++++--- src/error.ts | 5 ++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/controller/write.ts b/src/controller/write.ts index 4b61d302..2251b111 100644 --- a/src/controller/write.ts +++ b/src/controller/write.ts @@ -5,7 +5,7 @@ import { create as createError } from '@src/error'; // example call: /write?user=xx&lat=00.000&lon=00.000×tamp=1704063600000&hdop=0.0&altitude=0.000&speed=0.000&heading=000.0 -function errorChecking (req:Request, res:Response, next:NextFunction) { +async function errorChecking (req:Request, res:Response, next:NextFunction) { const errors = validationResult(req); if (!errors.isEmpty()) { const errorAsJson = { errors: errors.array()}; @@ -22,9 +22,13 @@ function errorChecking (req:Request, res:Response, next:NextFunction) { } // Regular Save logic from here - entry.create(req, res, next); + await entry.create(req, res, next); - res.send(req.query); + if (!res.locals.error) { + res.send(req.query); + } + + next(); } diff --git a/src/error.ts b/src/error.ts index 4f11a2f6..7b8d624d 100644 --- a/src/error.ts +++ b/src/error.ts @@ -7,7 +7,8 @@ export function create(res:Response, status:number = 500, message:string, next:N */ const error = new Error(message); res.status(status); - return next(error) + res.locals.error = true; // to let other middleware know that an error was called + next(error) } export function notFound(req: Request, res: Response, next: NextFunction) { @@ -17,7 +18,6 @@ export function notFound(req: Request, res: Response, next: NextFunction) { } export function handler(err: Error, req: Request, res: Response, next: NextFunction) { - const statusCode = res.statusCode !== 200 ? res.statusCode : 500; res.status(statusCode); @@ -38,6 +38,5 @@ export function handler(err: Error, req: Request, res: Response logger.error(responseBody); res.json(responseBody); - next(); } From 10391c5ddf571f2db74618cbcced60da0a9cfe89 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Mon, 29 Jan 2024 18:41:48 +0100 Subject: [PATCH 21/62] [Task] #10 writing basic non calculated data to file --- src/models/entry.ts | 99 ++++++++++++++++++++++++++++++--------------- src/scripts/file.ts | 34 +++++++++++----- types.d.ts | 19 ++++----- 3 files changed, 100 insertions(+), 52 deletions(-) diff --git a/src/models/entry.ts b/src/models/entry.ts index 5f9088f7..31977388 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -1,51 +1,84 @@ -import { NextFunction, Request, Response} from 'express'; +import { NextFunction, Request, Response } from 'express'; import { checkExact, query } from 'express-validator'; import { crypt } from '@src/scripts/crypt'; +import { create as createError } from '@src/error'; import * as file from '@src/scripts/file'; export const entry = { - create: async (req:Request, res:Response, next:NextFunction) => { - const fileObj:File.Obj= file.createFile(res, next); + create: async (req: Request, res: Response, next: NextFunction) => { + const fileObj: File.Obj = file.getFile(res, next); fileObj.content = await file.readAsJson(res, fileObj.path, next); - console.log(fileObj.content); - }, - validate: [ - query('user').isLength({ min: 2, max: 2 }), - query('lat').custom(checkNumber(-90, 90)), - query('lon').custom(checkNumber(-180, 180)), - query('timestamp').custom(checkTime), - query('hdop').custom(checkNumber(0, 100)), - query('altitude').custom(checkNumber(0, 10000)), - query('speed').custom(checkNumber(0, 300)), - query('heading').custom(checkNumber(0, 360)), + if (!fileObj.content?.entries) { + return createError(res, 500, "File Content unavailable: " + fileObj.path, next); + } + const entries = fileObj.content.entries; + const entry = {} as Models.IEntry; + + entry.altitude = Number(req.query.altitude); + + entry.hdop = Number(req.query.hdop); + entry.heading = Number(req.query.heading); + entry.index = entries.length; + entry.lat = Number(req.query.lat); + entry.lon = Number(req.query.lon); + entry.user = req.query.user as string; + // entry.time = getTime(); + // entry.speed = getSpeed() + if (entries.length) { // so there is a previous entry + // checkIgnore() + // newEntry.angle = getAngle(); + // newEntry.distance = getDistance() + } + + + + + + + entries.push(entry); + + + file.write(res, fileObj, next); + + + }, + validate: [ + query('user').isLength({ min: 2, max: 2 }), + query('lat').custom(checkNumber(-90, 90)), + query('lon').custom(checkNumber(-180, 180)), + query('timestamp').custom(checkTime), + query('hdop').custom(checkNumber(0, 100)), + query('altitude').custom(checkNumber(0, 10000)), + query('speed').custom(checkNumber(0, 300)), + query('heading').custom(checkNumber(0, 360, "integer")), query("key").custom(checkKey), - checkExact() + checkExact() // INFO: if message or any string gets added remember to escape - ] + ] } -export function checkNumber(min:number, max:number) { - return (value:string) => { +export function checkNumber(min: number, max: number, type: string = "float") { + return (value: string) => { if (!value) { throw new Error('is required'); } - if (value.length > 12) { - throw new Error('Should have a maximum of 11 digits'); - } - - const number = parseFloat(value); + if (value.length > 12) { + throw new Error('Should have a maximum of 11 digits'); + } + + const number = type == "float" ? parseFloat(value) : parseInt(value); if (isNaN(number) || number < min || number > max) { throw new Error(`Value should be between ${min} and ${max}`); } return true; - }; + }; } -export function checkTime(value:string) { +export function checkTime(value: string) { const timestamp = parseFloat(value); - + // Check if it's a number if (isNaN(timestamp)) { throw new Error('Timestamp should be a number'); @@ -60,20 +93,20 @@ export function checkTime(value:string) { if (process.env.NODE_ENV == "development") { return true; // dev testing convenience } - + const now = new Date(); const difference = now.getTime() - date.getTime(); const oneDayInMilliseconds = 24 * 60 * 60 * 1000; if (Math.abs(difference) >= oneDayInMilliseconds) { throw new Error('Timestamp should represent a date not further from server time than 1 day'); } - + return true } -function checkKey(value:string) { +function checkKey(value: string) { if (process.env.NODE_ENV != "production" && value == "test") { - return true; // dev testing convenience + return true; // dev testing convenience } if (!value) { @@ -81,13 +114,13 @@ function checkKey(value:string) { } value = decodeURIComponent(value); - + const hash = crypt(value); if (process.env.KEYB != hash) { if (process.env.NODE_ENV == "development") { - console.log(hash); - } + console.log(hash); + } throw new Error('Key does not match'); } diff --git a/src/scripts/file.ts b/src/scripts/file.ts index 9660fcf2..9988a67d 100644 --- a/src/scripts/file.ts +++ b/src/scripts/file.ts @@ -5,7 +5,7 @@ import { create as createError } from '@src/error'; import { NextFunction, Response } from 'express'; import logger from '@src/scripts/logger'; -export const createFile = (res: Response, next: NextFunction): File.Obj => { +export const getFile = (res: Response, next: NextFunction): File.Obj => { const date = new Date(); const formattedDate = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; const dirPath = path.resolve(__dirname, '../data'); @@ -20,29 +20,43 @@ export const createFile = (res: Response, next: NextFunction): File.Obj => { if (!fs.existsSync(filePath)) { // check if file exist fileExisted = false; try { - fs.writeFileSync(filePath, ''); + fs.writeFileSync(filePath, '{"entries": []}'); + logger.log(`file: ${filePath} did not exist, but created now`); } catch (err) { createError(res, 500, "File cannot be written to", next); } } - return { path: filePath, content: fileExisted ? undefined : '' }; // if the file did not exist before, the content is emptyString + return { path: filePath, content: fileExisted ? undefined : JSON.parse('{"entries": []}') }; // if the file did not exist before, the content is emptyString }; - - const readFileAsync = promisify(fs.readFile); -export async function readAsJson(res: Response, filePath: string, next: NextFunction): Promise { +export async function readAsJson(res: Response, filePath: string, next: NextFunction): Promise { const data = await readFileAsync(filePath, 'utf-8'); - if (data === '') { return ''; } - try { return JSON.parse(data); } catch (err) { - createError(res, 500, "File contains wrong content", next); - return undefined; + createError(res, 500, "File contains wrong content: " + filePath, next); } } + + +export const write = (res:Response, fileObj:File.Obj, next: NextFunction) => { + + if (!fs.existsSync(fileObj.path)) { // check if file exist + createError(res, 500, "Can not write to file that does not exist: " + fileObj.path, next); + } + try { + const content = JSON.stringify(fileObj.content); + fs.writeFileSync(fileObj.path, content); + fileObj.content = JSON.parse(content); + logger.log(`written to file: ${fileObj.path}`); + } catch (err) { + createError(res, 500, `File (${fileObj.path}) cannot be written to`, next); + } + + return fileObj; // if the file did not exist before, the content is emptyString +}; diff --git a/types.d.ts b/types.d.ts index 97039812..8341c1e6 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,10 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -type NumericRange = - ARR['length'] extends END ? ACC | START | END : - NumericRange; - namespace Response { interface Message { message: string; @@ -19,12 +15,16 @@ namespace Response { } namespace File { interface Obj { - path: string - content?: JSON | '' + path: string, + content?: Models.IEntries; } } namespace Models { + interface IEntries { + entries: Models.IEntry[] + } + interface IEntry { /** * height above ground in meters, as received by gps @@ -34,7 +34,7 @@ namespace Models { /** * Direction in degrees between two coordinate pairs: 0°-360° */ - angle: NumericRange<0, 360>, + angle: number, /** * object containing horizontal vertical and total distance, in meters @@ -48,7 +48,8 @@ namespace Models { /** * object containing horizontal vertical and total speed, in km/h */ - speeed: { + speed: { + gps: number; horizontal: number, vertical: number, total: number @@ -62,7 +63,7 @@ namespace Models { /** * Heading or Bearing as recieved from gps */ - heading: NumericRange<0, 360>, + heading: number, /** * lat From 552dc323ad65aa0a8b234e7ea712a9a2f354b139 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 30 Jan 2024 12:04:04 +0100 Subject: [PATCH 22/62] [Fix] #10 avoid Header Modification after sending the request --- src/controller/write.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/controller/write.ts b/src/controller/write.ts index 2251b111..e4c6ef05 100644 --- a/src/controller/write.ts +++ b/src/controller/write.ts @@ -24,11 +24,14 @@ async function errorChecking (req:Request, res:Response, next:NextFunction) { // Regular Save logic from here await entry.create(req, res, next); - if (!res.locals.error) { + if (!res.locals.error) { res.send(req.query); - } - - next(); + } else { + /* at this point error handling already happend, + * or the request has already been send + * therefor there is no need for it again (only middleware to follow at this point) */ + next(); + } } From 7bfdceda163496705c6612da2c8cfce0d9482d5b Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 30 Jan 2024 13:34:49 +0100 Subject: [PATCH 23/62] [Task] #10 JSON Data pretty output --- src/scripts/file.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/file.ts b/src/scripts/file.ts index 9988a67d..5f0cbe5d 100644 --- a/src/scripts/file.ts +++ b/src/scripts/file.ts @@ -50,7 +50,7 @@ export const write = (res:Response, fileObj:File.Obj, next: NextFunction) => { createError(res, 500, "Can not write to file that does not exist: " + fileObj.path, next); } try { - const content = JSON.stringify(fileObj.content); + const content = JSON.stringify(fileObj.content, undefined, 2); fs.writeFileSync(fileObj.path, content); fileObj.content = JSON.parse(content); logger.log(`written to file: ${fileObj.path}`); From 0bee5737f0d72660b66a085157ed0959429a64d1 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 30 Jan 2024 13:51:49 +0100 Subject: [PATCH 24/62] [Task] #32 update types to reflect subobjects of entry --- types.d.ts | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/types.d.ts b/types.d.ts index 8341c1e6..c829a45e 100644 --- a/types.d.ts +++ b/types.d.ts @@ -39,21 +39,12 @@ namespace Models { /** * object containing horizontal vertical and total distance, in meters */ - distance: { - horizontal: number, - vertical: number, - total: number - }, + distance: Models.IDistance, /** * object containing horizontal vertical and total speed, in km/h */ - speed: { - gps: number; - horizontal: number, - vertical: number, - total: number - }, + speed: Models.ISpeed, /** * index, position of the entry point in the chain @@ -90,17 +81,31 @@ namespace Models { /** * time object containing UNIX timestamps with milliseconds, gps creation time (as recieved via gps), server time (when the server recieved and computed it), differce to last entry (time between waypoints), upload time differnce */ - time: { - created: number, - recieved: number, - uploadDuration: number, - diff: number - createdString: string - }, + time: Models.time, /** * user as recieved */ user: string } + + interface ITime { + created: number, + recieved: number, + uploadDuration: number, + diff: number + createdString: string + } + + interface ISpeed { + gps: number; + horizontal: number, + vertical: number, + total: number + } + interface IDistance { + horizontal: number, + vertical: number, + total: number + } } From 9e801fe6d67f24a2cf79901eabebc406fae52482 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 30 Jan 2024 13:52:14 +0100 Subject: [PATCH 25/62] [Task] #10 write time --- src/models/entry.ts | 15 ++++++--------- src/scripts/time.ts | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/models/entry.ts b/src/models/entry.ts index 31977388..285a061d 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -3,6 +3,8 @@ import { checkExact, query } from 'express-validator'; import { crypt } from '@src/scripts/crypt'; import { create as createError } from '@src/error'; import * as file from '@src/scripts/file'; +import { getTime } from '@src/scripts/time'; +import { getSpeed } from '@src/scripts/speed'; export const entry = { @@ -17,32 +19,27 @@ export const entry = { const entry = {} as Models.IEntry; entry.altitude = Number(req.query.altitude); - entry.hdop = Number(req.query.hdop); entry.heading = Number(req.query.heading); entry.index = entries.length; entry.lat = Number(req.query.lat); entry.lon = Number(req.query.lon); entry.user = req.query.user as string; - // entry.time = getTime(); - // entry.speed = getSpeed() + //entry.speed = getSpeed(Number(req.query.speed)) if (entries.length) { // so there is a previous entry + entry.time = getTime(Number(req.query.timestamp), entries.at(-1)); // checkIgnore() // newEntry.angle = getAngle(); // newEntry.distance = getDistance() + } else { + entry.time = getTime(Number(req.query.timestamp)); } - - - - entries.push(entry); - file.write(res, fileObj, next); - }, validate: [ query('user').isLength({ min: 2, max: 2 }), diff --git a/src/scripts/time.ts b/src/scripts/time.ts index e69de29b..6a988ec9 100644 --- a/src/scripts/time.ts +++ b/src/scripts/time.ts @@ -0,0 +1,21 @@ +export function getTime(time: number, entry?: Models.IEntry): Models.ITime { + const now = new Date(); + const created = Number(time); + const recieved = now.getTime(); + const uploadDuration = recieved - created; + const createdString = now.toLocaleString("de-DE", { + weekday: "long", + year: "numeric", + month: "long", + day: "numeric", + }); + const diff = entry ? created - entry.time.created : -1; + + return { + created: created, + recieved: recieved, + uploadDuration: uploadDuration, + diff: diff, + createdString: createdString + } +} \ No newline at end of file From b801af41c2757b6a24cf4766d609732d78fca6e0 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 30 Jan 2024 14:04:51 +0100 Subject: [PATCH 26/62] [Task] #32 added logging for time edgecases --- src/scripts/time.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/scripts/time.ts b/src/scripts/time.ts index 6a988ec9..453857f4 100644 --- a/src/scripts/time.ts +++ b/src/scripts/time.ts @@ -1,3 +1,5 @@ +import logger from '@src/scripts/logger'; + export function getTime(time: number, entry?: Models.IEntry): Models.ITime { const now = new Date(); const created = Number(time); @@ -11,6 +13,13 @@ export function getTime(time: number, entry?: Models.IEntry): Models.ITime { }); const diff = entry ? created - entry.time.created : -1; + if (uploadDuration < 0) { + logger.error(`upload Duration is negative: ${createdString}, index: ${entry ? entry.index + 1 : 0}`); + } + if (entry && entry.time.created > created) { // maybe this could happend due to the async nature, but due to uncertainty logging is enabled + logger.error(`previous timestamp is more recent: ${createdString}, index: ${entry?.index + 1}`); + } + return { created: created, recieved: recieved, From b7360b27c583284679ee31a604b69d9e39ee7ce5 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 30 Jan 2024 23:26:47 +0100 Subject: [PATCH 27/62] [Task] #10 output seconds --- src/scripts/time.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/scripts/time.ts b/src/scripts/time.ts index 453857f4..f31bb391 100644 --- a/src/scripts/time.ts +++ b/src/scripts/time.ts @@ -10,7 +10,9 @@ export function getTime(time: number, entry?: Models.IEntry): Models.ITime { year: "numeric", month: "long", day: "numeric", - }); + hour12: false, + minute: '2-digit', + }) + ":" + now.getSeconds(); const diff = entry ? created - entry.time.created : -1; if (uploadDuration < 0) { From 4cd1c07bada66544c4e13b1208dd2ec1daa4c9ae Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 30 Jan 2024 23:31:31 +0100 Subject: [PATCH 28/62] [Task] #10 calculate distance based on lat and lon --- src/models/entry.ts | 13 ++++++++----- src/scripts/distance.ts | 30 ++++++++++++++++++++++++++++++ src/scripts/time.ts | 1 + 3 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 src/scripts/distance.ts diff --git a/src/models/entry.ts b/src/models/entry.ts index 285a061d..2ebe35c5 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -4,7 +4,8 @@ import { crypt } from '@src/scripts/crypt'; import { create as createError } from '@src/error'; import * as file from '@src/scripts/file'; import { getTime } from '@src/scripts/time'; -import { getSpeed } from '@src/scripts/speed'; +//import { getSpeed } from '@src/scripts/speed'; +import { getDistance } from '@src/scripts/distance'; export const entry = { @@ -16,6 +17,7 @@ export const entry = { return createError(res, 500, "File Content unavailable: " + fileObj.path, next); } const entries = fileObj.content.entries; + const lastEntry = fileObj.content.entries.at(-1); const entry = {} as Models.IEntry; entry.altitude = Number(req.query.altitude); @@ -25,14 +27,15 @@ export const entry = { entry.lat = Number(req.query.lat); entry.lon = Number(req.query.lon); entry.user = req.query.user as string; - //entry.speed = getSpeed(Number(req.query.speed)) - if (entries.length) { // so there is a previous entry - entry.time = getTime(Number(req.query.timestamp), entries.at(-1)); + if (lastEntry) { // so there is a previous entry + entry.time = getTime(Number(req.query.timestamp), lastEntry); // checkIgnore() // newEntry.angle = getAngle(); - // newEntry.distance = getDistance() + entry.distance = getDistance(entry, lastEntry) + //entry.speed = getSpeed(Number(req.query.speed) , lastEntry); } else { entry.time = getTime(Number(req.query.timestamp)); + //entry.speed = getSpeed(Number(req.query.speed)) } diff --git a/src/scripts/distance.ts b/src/scripts/distance.ts new file mode 100644 index 00000000..79159b87 --- /dev/null +++ b/src/scripts/distance.ts @@ -0,0 +1,30 @@ +export function getDistance(entry: Models.IEntry, lastEntry: Models.IEntry): Models.IDistance { + const horizontal = calculateDistance({ lat: entry.lat, lon: entry.lon }, { lat: lastEntry.lat, lon: lastEntry.lon }); + const vertical = entry.altitude - lastEntry.altitude; + const total = horizontal + Math.abs(vertical); + + return { + horizontal: horizontal, + vertical: vertical, + total: total + } +} + +function toRad(x: number): number { + return x * Math.PI / 180; +} + +function calculateDistance(coord1: { lat: number, lon: number }, coord2: { lat: number, lon: number }): number { + const R = 6371000; // radius of the Earth in meters + const dLat = toRad(coord2.lat - coord1.lat); + const dLon = toRad(coord2.lon - coord1.lon); + + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(toRad(coord1.lat)) * Math.cos(toRad(coord2.lat)) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + const distance = R * c; + + return distance; +} \ No newline at end of file diff --git a/src/scripts/time.ts b/src/scripts/time.ts index f31bb391..e15da3e4 100644 --- a/src/scripts/time.ts +++ b/src/scripts/time.ts @@ -10,6 +10,7 @@ export function getTime(time: number, entry?: Models.IEntry): Models.ITime { year: "numeric", month: "long", day: "numeric", + hour: '2-digit', hour12: false, minute: '2-digit', }) + ":" + now.getSeconds(); From f8799b2bcd273400dd3f2f5f61ea90fb309d1d4f Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 31 Jan 2024 00:48:53 +0100 Subject: [PATCH 29/62] [Task] #32 writing tests for time and distance --- src/scripts/time.ts | 3 +- src/tests/write.test.ts | 81 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/scripts/time.ts b/src/scripts/time.ts index e15da3e4..d4bc6d03 100644 --- a/src/scripts/time.ts +++ b/src/scripts/time.ts @@ -13,7 +13,8 @@ export function getTime(time: number, entry?: Models.IEntry): Models.ITime { hour: '2-digit', hour12: false, minute: '2-digit', - }) + ":" + now.getSeconds(); + second: '2-digit' + }); const diff = entry ? created - entry.time.created : -1; if (uploadDuration < 0) { diff --git a/src/tests/write.test.ts b/src/tests/write.test.ts index 2a61ef8e..4154693a 100644 --- a/src/tests/write.test.ts +++ b/src/tests/write.test.ts @@ -2,7 +2,7 @@ import axios, { AxiosError } from 'axios'; import fs from "fs"; import path from "path"; -async function callServer(timestamp = new Date().getTime(), query: string, expectStatus: number = 200, method:string = "HEAD") { +async function callServer(timestamp = new Date().getTime(), query: string, expectStatus: number = 200, method: string = "HEAD") { const url = new URL("http://localhost:80/write?"); url.search = "?" + query; const params = new URLSearchParams(url.search); @@ -10,7 +10,7 @@ async function callServer(timestamp = new Date().getTime(), query: string, expec url.search = params.toString(); let response; - if (expectStatus == 200) { + if (expectStatus == 200) { if (method == "GET") { response = await axios.get(url.toString()); } else { @@ -30,7 +30,7 @@ async function callServer(timestamp = new Date().getTime(), query: string, expec describe('HEAD /write', () => { it('with all parameters correctly set it should succeed', async () => { callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200); - }); + }); it('without key it sends 403', async () => { callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0", 403); @@ -72,16 +72,77 @@ describe('HEAD /write', () => { describe("GET /write", () => { + const date = new Date(); + const formattedDate = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + const dirPath = path.resolve(__dirname, '../../dist/data/'); + const filePath = path.resolve(dirPath, `data-${formattedDate}.json`); + it('there should a file of the current date', async () => { - await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + await callServer(undefined, "user=xx&lat=52.51451&lon=13.35105×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); - const date = new Date(); - const formattedDate = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; - const dirPath = path.resolve(__dirname, '../../dist/data/'); - const filePath = path.resolve(dirPath, `data-${formattedDate}.json`); - console.log(filePath); fs.access(filePath, fs.constants.F_OK, (err) => { expect(err).toBeFalsy(); }); }); -}); \ No newline at end of file + + it('the file contains valid JSON', async () => { + fs.readFile(filePath, 'utf8', (err, data) => { + expect(err).toBeFalsy(); + try { + JSON.parse(data); + } catch (e) { + expect(e).toBeFalsy(); + } + }); + }); + + it('after second call and the JSON entries length is 2', () => { + return new Promise(done => { + // Increase the timeout for this test + setTimeout(async () => { + await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50.0&altitude=5001.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + const data = fs.readFileSync(filePath); + const jsonData = JSON.parse(data.toString()); + + expect(jsonData.entries.length).toBe(2); + + done(); + }, 2000); + }) + }); + + it('the time is correct', () => { + const data = fs.readFileSync(filePath); + const jsonData = JSON.parse(data.toString()); + const lastEntry = jsonData.entries.at(-1) + + expect(lastEntry.time.created).toBeGreaterThan(date.getTime()); + expect(lastEntry.time.diff).toBeGreaterThan(2000); + expect(lastEntry.time.diff).toBeLessThan(3000); + + + const germanDayPattern = "(Montag|Dienstag|Mittwoch|Donnerstag|Freitag|Samstag|Sonntag)"; + const dayOfMonthPattern = "(0[1-9]|[12][0-9]|3[01])"; + const germanMonthPattern = "(Januar|Februar|März|April|Mai|Juni|Juli|August|September|Oktober|November|Dezember)"; + const yearPattern = "(\\d{4})"; + const timePattern = "([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]"; + + const pattern = new RegExp(`^${germanDayPattern}, ${dayOfMonthPattern}. ${germanMonthPattern} ${yearPattern} um ${timePattern}$`); + const string = lastEntry.time.createdString; + expect(pattern.test(string)).toBeTruthy(); + + }); + + it('the distance is correct', () => { + const data = fs.readFileSync(filePath); + const jsonData = JSON.parse(data.toString()); + const lastEntry = jsonData.entries.at(-1) + + expect(lastEntry.distance.horizontal).toBeCloseTo(1813.926); + expect(lastEntry.distance.vertical).toBe(1); + expect(lastEntry.distance.total).toBeCloseTo(1814.926); + }); + + + +}); From e39b8a7b22714d6399d8eb4eef3209fcd4ee4cd6 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 31 Jan 2024 00:57:43 +0100 Subject: [PATCH 30/62] [Task] #32 change distance calculation to use pythagoras --- src/scripts/distance.ts | 2 +- src/tests/write.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/scripts/distance.ts b/src/scripts/distance.ts index 79159b87..36c4637e 100644 --- a/src/scripts/distance.ts +++ b/src/scripts/distance.ts @@ -1,7 +1,7 @@ export function getDistance(entry: Models.IEntry, lastEntry: Models.IEntry): Models.IDistance { const horizontal = calculateDistance({ lat: entry.lat, lon: entry.lon }, { lat: lastEntry.lat, lon: lastEntry.lon }); const vertical = entry.altitude - lastEntry.altitude; - const total = horizontal + Math.abs(vertical); + const total = Math.sqrt(horizontal * horizontal + vertical * vertical); return { horizontal: horizontal, diff --git a/src/tests/write.test.ts b/src/tests/write.test.ts index 4154693a..741a2d62 100644 --- a/src/tests/write.test.ts +++ b/src/tests/write.test.ts @@ -100,7 +100,7 @@ describe("GET /write", () => { return new Promise(done => { // Increase the timeout for this test setTimeout(async () => { - await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50.0&altitude=5001.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50.0&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); const data = fs.readFileSync(filePath); const jsonData = JSON.parse(data.toString()); @@ -139,8 +139,8 @@ describe("GET /write", () => { const lastEntry = jsonData.entries.at(-1) expect(lastEntry.distance.horizontal).toBeCloseTo(1813.926); - expect(lastEntry.distance.vertical).toBe(1); - expect(lastEntry.distance.total).toBeCloseTo(1814.926); + expect(lastEntry.distance.vertical).toBe(-1000); + expect(lastEntry.distance.total).toBeCloseTo(2071.311); }); From 6393efc21f335a7a4d028135982ad61e0812bdb9 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 31 Jan 2024 01:20:35 +0100 Subject: [PATCH 31/62] [Task] #38 add favicon --- httpdocs/favicon.ico | Bin 0 -> 1150 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 httpdocs/favicon.ico diff --git a/httpdocs/favicon.ico b/httpdocs/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..040a17f731119f6a80ecfd6391ed9bbb0eeb2a0d GIT binary patch literal 1150 zcmbVKOG*Pl5UmhHP}Bqw-I)k3at$w#E4cL*USS}gBe-yFG$`(}&KU$x5Mp4!kn)v1 zJyWSv6R^WeSHDkBb<;#O{Mv28f0ynLh%Shz2Y><-cuYjllH;s%NZE>ABtwNDIT<1U zRIqVfX{E5I$i72nk1Z*Wmf;-Zf9_-M;qt26NPf!`O#bg)hQ%Fue#?3J$XTzqj^5Pl zWi)4VBi!S_ybbL)^~sd0ccirbGf&F*rFdH&BQLm}@!V=ZjJ z=_Gt#_R;zJVB?-kd!&9_aWD4Z&6MR^``Wy$8}C&QdXMJIc28bbbK*b8d5&J0KNftW lHK{)zh#v2Ve$bJ0m1CWEfELgM>cAJUD1dqmZVip$`vtMm3?%>n literal 0 HcmV?d00001 From 353817cf460529d18ba48a1c9dcc2d530ff7035a Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 31 Jan 2024 12:55:44 +0100 Subject: [PATCH 32/62] [Task] #32 time converted to seconds --- src/scripts/time.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/time.ts b/src/scripts/time.ts index d4bc6d03..f6adbc7e 100644 --- a/src/scripts/time.ts +++ b/src/scripts/time.ts @@ -4,7 +4,7 @@ export function getTime(time: number, entry?: Models.IEntry): Models.ITime { const now = new Date(); const created = Number(time); const recieved = now.getTime(); - const uploadDuration = recieved - created; + const uploadDuration = (recieved - created) / 1000; const createdString = now.toLocaleString("de-DE", { weekday: "long", year: "numeric", @@ -15,7 +15,7 @@ export function getTime(time: number, entry?: Models.IEntry): Models.ITime { minute: '2-digit', second: '2-digit' }); - const diff = entry ? created - entry.time.created : -1; + const diff = entry ? (created - entry.time.created) / 1000 : undefined; if (uploadDuration < 0) { logger.error(`upload Duration is negative: ${createdString}, index: ${entry ? entry.index + 1 : 0}`); From f7df2d89cb42f203fd1cafa40fc3352396641577 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 31 Jan 2024 12:56:13 +0100 Subject: [PATCH 33/62] [Taskk] #32 speed calculation and output and tests --- src/models/entry.ts | 7 ++++--- src/scripts/speed.ts | 19 +++++++++++++++++++ src/tests/write.test.ts | 13 +++++++++++-- types.d.ts | 8 ++++---- 4 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 src/scripts/speed.ts diff --git a/src/models/entry.ts b/src/models/entry.ts index 2ebe35c5..bbbdd752 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -4,7 +4,7 @@ import { crypt } from '@src/scripts/crypt'; import { create as createError } from '@src/error'; import * as file from '@src/scripts/file'; import { getTime } from '@src/scripts/time'; -//import { getSpeed } from '@src/scripts/speed'; +import { getSpeed } from '@src/scripts/speed'; import { getDistance } from '@src/scripts/distance'; @@ -32,10 +32,11 @@ export const entry = { // checkIgnore() // newEntry.angle = getAngle(); entry.distance = getDistance(entry, lastEntry) - //entry.speed = getSpeed(Number(req.query.speed) , lastEntry); + entry.speed = getSpeed(Number(req.query.speed), entry); + } else { entry.time = getTime(Number(req.query.timestamp)); - //entry.speed = getSpeed(Number(req.query.speed)) + entry.speed = getSpeed(Number(req.query.speed)) } diff --git a/src/scripts/speed.ts b/src/scripts/speed.ts new file mode 100644 index 00000000..b0591c07 --- /dev/null +++ b/src/scripts/speed.ts @@ -0,0 +1,19 @@ +export function getSpeed(speed: number, entry?: Models.IEntry): Models.ISpeed { + const gps = speed; + let horizontal; + let vertical; + let total; + + if (entry) { + horizontal = entry.distance.horizontal / entry.time.diff; + vertical = entry.distance.vertical / entry.time.diff; + total = entry.distance.total / entry.time.diff; + } + + return { + gps: gps, + horizontal: horizontal, + vertical: vertical, + total: total + } +} \ No newline at end of file diff --git a/src/tests/write.test.ts b/src/tests/write.test.ts index 741a2d62..d0de6f23 100644 --- a/src/tests/write.test.ts +++ b/src/tests/write.test.ts @@ -117,8 +117,8 @@ describe("GET /write", () => { const lastEntry = jsonData.entries.at(-1) expect(lastEntry.time.created).toBeGreaterThan(date.getTime()); - expect(lastEntry.time.diff).toBeGreaterThan(2000); - expect(lastEntry.time.diff).toBeLessThan(3000); + expect(lastEntry.time.diff).toBeGreaterThan(2); + expect(lastEntry.time.diff).toBeLessThan(3); const germanDayPattern = "(Montag|Dienstag|Mittwoch|Donnerstag|Freitag|Samstag|Sonntag)"; @@ -143,6 +143,15 @@ describe("GET /write", () => { expect(lastEntry.distance.total).toBeCloseTo(2071.311); }); + it('the speed is correct', () => { + const data = fs.readFileSync(filePath); + const jsonData = JSON.parse(data.toString()); + const lastEntry = jsonData.entries.at(-1) + expect(lastEntry.speed.gps).toBe(150); + expect(lastEntry.speed.horizontal).toBeCloseTo(865.836); + expect(lastEntry.speed.vertical).toBeCloseTo(-477.326); + expect(lastEntry.speed.total).toBeCloseTo(988.69); + }); }); diff --git a/types.d.ts b/types.d.ts index c829a45e..a3674205 100644 --- a/types.d.ts +++ b/types.d.ts @@ -93,15 +93,15 @@ namespace Models { created: number, recieved: number, uploadDuration: number, - diff: number + diff?: number createdString: string } interface ISpeed { gps: number; - horizontal: number, - vertical: number, - total: number + horizontal?: number, + vertical?: number, + total?: number } interface IDistance { horizontal: number, From 976096a9111319d5553681be76a87d935a626ccd Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 31 Jan 2024 13:47:19 +0100 Subject: [PATCH 34/62] [Task] #32 speed tests --- src/tests/write.test.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/tests/write.test.ts b/src/tests/write.test.ts index d0de6f23..9b61d7d7 100644 --- a/src/tests/write.test.ts +++ b/src/tests/write.test.ts @@ -27,6 +27,10 @@ async function callServer(timestamp = new Date().getTime(), query: string, expec } } +function isInRange(actual:string | number, expected:number, range:number) { + return Math.abs(Number(actual) - expected) <= range; +} + describe('HEAD /write', () => { it('with all parameters correctly set it should succeed', async () => { callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200); @@ -118,7 +122,7 @@ describe("GET /write", () => { expect(lastEntry.time.created).toBeGreaterThan(date.getTime()); expect(lastEntry.time.diff).toBeGreaterThan(2); - expect(lastEntry.time.diff).toBeLessThan(3); + expect(lastEntry.time.diff).toBeLessThan(3); const germanDayPattern = "(Montag|Dienstag|Mittwoch|Donnerstag|Freitag|Samstag|Sonntag)"; @@ -130,7 +134,7 @@ describe("GET /write", () => { const pattern = new RegExp(`^${germanDayPattern}, ${dayOfMonthPattern}. ${germanMonthPattern} ${yearPattern} um ${timePattern}$`); const string = lastEntry.time.createdString; expect(pattern.test(string)).toBeTruthy(); - + }); it('the distance is correct', () => { @@ -148,10 +152,9 @@ describe("GET /write", () => { const jsonData = JSON.parse(data.toString()); const lastEntry = jsonData.entries.at(-1) - expect(lastEntry.speed.gps).toBe(150); - expect(lastEntry.speed.horizontal).toBeCloseTo(865.836); - expect(lastEntry.speed.vertical).toBeCloseTo(-477.326); - expect(lastEntry.speed.total).toBeCloseTo(988.69); + expect(isInRange(lastEntry.speed.horizontal, 871, 6)).toBe(true); + expect(isInRange(lastEntry.speed.vertical, -479, 6)).toBe(true); + expect(isInRange(lastEntry.speed.total, 995, 6)).toBe(true); }); }); From e9d0bbe66fc2adc5efffeae021ede1c54d799b8e Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 31 Jan 2024 13:52:39 +0100 Subject: [PATCH 35/62] [Task] #33 add ignore --- src/models/entry.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/models/entry.ts b/src/models/entry.ts index bbbdd752..d152c09d 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -27,9 +27,10 @@ export const entry = { entry.lat = Number(req.query.lat); entry.lon = Number(req.query.lon); entry.user = req.query.user as string; + entry.ignore = false; if (lastEntry) { // so there is a previous entry entry.time = getTime(Number(req.query.timestamp), lastEntry); - // checkIgnore() + entry.ignore = checkIgnore(lastEntry); // newEntry.angle = getAngle(); entry.distance = getDistance(entry, lastEntry) entry.speed = getSpeed(Number(req.query.speed), entry); @@ -105,6 +106,19 @@ export function checkTime(value: string) { return true } +function checkIgnore(lastEntry: Models.IEntry): boolean { + let threshold = 6; // hdop not allowed to be higher + const maxThreshold = 25; + + // if older the previous entry the higher the threshold + if (lastEntry.time.diff && lastEntry.time.diff > 32) { + threshold += Math.min(lastEntry.time.diff / 60, maxThreshold); + } + + return lastEntry.hdop > threshold; +} + + function checkKey(value: string) { if (process.env.NODE_ENV != "production" && value == "test") { return true; // dev testing convenience @@ -126,4 +140,4 @@ function checkKey(value: string) { } return true; -} \ No newline at end of file +} From e97d965722367ae90e3483eac4e2a2c3944134b7 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 31 Jan 2024 20:13:12 +0100 Subject: [PATCH 36/62] [Task] #32 test finetuning --- src/models/entry.ts | 10 +++++---- src/tests/write.test.ts | 50 +++++++++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/models/entry.ts b/src/models/entry.ts index d152c09d..d2fa3512 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -30,7 +30,7 @@ export const entry = { entry.ignore = false; if (lastEntry) { // so there is a previous entry entry.time = getTime(Number(req.query.timestamp), lastEntry); - entry.ignore = checkIgnore(lastEntry); + lastEntry.ignore = checkIgnore(lastEntry, entry); // newEntry.angle = getAngle(); entry.distance = getDistance(entry, lastEntry) entry.speed = getSpeed(Number(req.query.speed), entry); @@ -106,12 +106,14 @@ export function checkTime(value: string) { return true } -function checkIgnore(lastEntry: Models.IEntry): boolean { +function checkIgnore(lastEntry: Models.IEntry, entry:Models.IEntry): boolean { let threshold = 6; // hdop not allowed to be higher const maxThreshold = 25; - // if older the previous entry the higher the threshold - if (lastEntry.time.diff && lastEntry.time.diff > 32) { + const timing = Math.max(lastEntry.time.diff, entry.time.diff) + + // Threshold increases with older previous entries or farther future entries. + if (timing > 32 ) { threshold += Math.min(lastEntry.time.diff / 60, maxThreshold); } diff --git a/src/tests/write.test.ts b/src/tests/write.test.ts index 9b61d7d7..0d4971a8 100644 --- a/src/tests/write.test.ts +++ b/src/tests/write.test.ts @@ -27,7 +27,7 @@ async function callServer(timestamp = new Date().getTime(), query: string, expec } } -function isInRange(actual:string | number, expected:number, range:number) { +function isInRange(actual: string | number, expected: number, range: number) { return Math.abs(Number(actual) - expected) <= range; } @@ -82,7 +82,7 @@ describe("GET /write", () => { const filePath = path.resolve(dirPath, `data-${formattedDate}.json`); it('there should a file of the current date', async () => { - await callServer(undefined, "user=xx&lat=52.51451&lon=13.35105×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + await callServer(undefined, "user=xx&lat=52.51451&lon=13.35105×tamp=R3Pl4C3&hdop=20.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); fs.access(filePath, fs.constants.F_OK, (err) => { expect(err).toBeFalsy(); @@ -104,7 +104,7 @@ describe("GET /write", () => { return new Promise(done => { // Increase the timeout for this test setTimeout(async () => { - await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50.0&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); const data = fs.readFileSync(filePath); const jsonData = JSON.parse(data.toString()); @@ -118,11 +118,11 @@ describe("GET /write", () => { it('the time is correct', () => { const data = fs.readFileSync(filePath); const jsonData = JSON.parse(data.toString()); - const lastEntry = jsonData.entries.at(-1) + const entry = jsonData.entries.at(-1) - expect(lastEntry.time.created).toBeGreaterThan(date.getTime()); - expect(lastEntry.time.diff).toBeGreaterThan(2); - expect(lastEntry.time.diff).toBeLessThan(3); + expect(entry.time.created).toBeGreaterThan(date.getTime()); + expect(entry.time.diff).toBeGreaterThan(2); + expect(entry.time.diff).toBeLessThan(3); const germanDayPattern = "(Montag|Dienstag|Mittwoch|Donnerstag|Freitag|Samstag|Sonntag)"; @@ -132,7 +132,7 @@ describe("GET /write", () => { const timePattern = "([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]"; const pattern = new RegExp(`^${germanDayPattern}, ${dayOfMonthPattern}. ${germanMonthPattern} ${yearPattern} um ${timePattern}$`); - const string = lastEntry.time.createdString; + const string = entry.time.createdString; expect(pattern.test(string)).toBeTruthy(); }); @@ -140,21 +140,37 @@ describe("GET /write", () => { it('the distance is correct', () => { const data = fs.readFileSync(filePath); const jsonData = JSON.parse(data.toString()); - const lastEntry = jsonData.entries.at(-1) + const entry = jsonData.entries.at(-1) - expect(lastEntry.distance.horizontal).toBeCloseTo(1813.926); - expect(lastEntry.distance.vertical).toBe(-1000); - expect(lastEntry.distance.total).toBeCloseTo(2071.311); + expect(entry.distance.horizontal).toBeCloseTo(1813.926); + expect(entry.distance.vertical).toBe(-1000); + expect(entry.distance.total).toBeCloseTo(2071.311); }); it('the speed is correct', () => { const data = fs.readFileSync(filePath); const jsonData = JSON.parse(data.toString()); - const lastEntry = jsonData.entries.at(-1) + const entry = jsonData.entries.at(-1) - expect(isInRange(lastEntry.speed.horizontal, 871, 6)).toBe(true); - expect(isInRange(lastEntry.speed.vertical, -479, 6)).toBe(true); - expect(isInRange(lastEntry.speed.total, 995, 6)).toBe(true); + expect(isInRange(entry.speed.horizontal, 870, 10)).toBe(true); + expect(isInRange(entry.speed.vertical, -478, 10)).toBe(true); + expect(isInRange(entry.speed.total, 992, 10)).toBe(true); }); -}); + it('check ignore', async () => { + let data = fs.readFileSync(filePath); + let jsonData = JSON.parse(data.toString()); + let entry = jsonData.entries[1]; + const lastEntry = jsonData.entries[0]; + + expect(entry.ignore).toBe(false); // current one to be false allways + expect(lastEntry.ignore).toBe(true); // last one to high hdop to be true + + await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + data = fs.readFileSync(filePath); // rereading the data + jsonData = JSON.parse(data.toString()); + entry = jsonData.entries[1]; // same data point, but not last now therefore ignore true + expect(entry.ignore).toBe(true); + }); + +}); \ No newline at end of file From 1a89b42591aa424ae7ea71d99793764b4c790d88 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 31 Jan 2024 20:52:21 +0100 Subject: [PATCH 37/62] [Task] #32 add angle between entries --- src/models/entry.ts | 12 +++++++----- src/scripts/angle.ts | 9 +++++++++ types.d.ts | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 src/scripts/angle.ts diff --git a/src/models/entry.ts b/src/models/entry.ts index d2fa3512..52d53953 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -6,6 +6,7 @@ import * as file from '@src/scripts/file'; import { getTime } from '@src/scripts/time'; import { getSpeed } from '@src/scripts/speed'; import { getDistance } from '@src/scripts/distance'; +import { getAngle } from '@src/scripts/angle'; export const entry = { @@ -28,14 +29,15 @@ export const entry = { entry.lon = Number(req.query.lon); entry.user = req.query.user as string; entry.ignore = false; + if (lastEntry) { // so there is a previous entry entry.time = getTime(Number(req.query.timestamp), lastEntry); lastEntry.ignore = checkIgnore(lastEntry, entry); - // newEntry.angle = getAngle(); + entry.angle = getAngle(lastEntry, entry); entry.distance = getDistance(entry, lastEntry) entry.speed = getSpeed(Number(req.query.speed), entry); - } else { + entry.angle = undefined; entry.time = getTime(Number(req.query.timestamp)); entry.speed = getSpeed(Number(req.query.speed)) } @@ -106,15 +108,15 @@ export function checkTime(value: string) { return true } -function checkIgnore(lastEntry: Models.IEntry, entry:Models.IEntry): boolean { +function checkIgnore(lastEntry: Models.IEntry, entry: Models.IEntry): boolean { let threshold = 6; // hdop not allowed to be higher const maxThreshold = 25; const timing = Math.max(lastEntry.time.diff, entry.time.diff) // Threshold increases with older previous entries or farther future entries. - if (timing > 32 ) { - threshold += Math.min(lastEntry.time.diff / 60, maxThreshold); + if (timing > 32) { + threshold += Math.min(lastEntry.time.diff / 60, maxThreshold); } return lastEntry.hdop > threshold; diff --git a/src/scripts/angle.ts b/src/scripts/angle.ts new file mode 100644 index 00000000..e6ea9728 --- /dev/null +++ b/src/scripts/angle.ts @@ -0,0 +1,9 @@ +export function getAngle(lastEntry: Models.IEntry, entry: Models.IEntry): number { + const dLon = (entry.lon - lastEntry.lon) * Math.PI / 180; + const lat1 = lastEntry.lat * Math.PI / 180; + const lat2 = entry.lat * Math.PI / 180; + const y = Math.sin(dLon) * Math.cos(lat2); + const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon); + const angle = (Math.atan2(y, x) * 180 / Math.PI + 360) % 360; + return angle; +} \ No newline at end of file diff --git a/types.d.ts b/types.d.ts index a3674205..7cb4c689 100644 --- a/types.d.ts +++ b/types.d.ts @@ -34,7 +34,7 @@ namespace Models { /** * Direction in degrees between two coordinate pairs: 0°-360° */ - angle: number, + angle?: number, /** * object containing horizontal vertical and total distance, in meters From c346ffb4b24359006f554b6da5abde6ffa29b6a7 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 31 Jan 2024 20:53:27 +0100 Subject: [PATCH 38/62] [Task] #32 test for angle, extracted getData function --- src/tests/write.test.ts | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/tests/write.test.ts b/src/tests/write.test.ts index 0d4971a8..14d030ce 100644 --- a/src/tests/write.test.ts +++ b/src/tests/write.test.ts @@ -27,6 +27,11 @@ async function callServer(timestamp = new Date().getTime(), query: string, expec } } +function getData(filePath:string) { + const data = fs.readFileSync(filePath); + return JSON.parse(data.toString()); +} + function isInRange(actual: string | number, expected: number, range: number) { return Math.abs(Number(actual) - expected) <= range; } @@ -105,8 +110,7 @@ describe("GET /write", () => { // Increase the timeout for this test setTimeout(async () => { await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); - const data = fs.readFileSync(filePath); - const jsonData = JSON.parse(data.toString()); + const jsonData = getData(filePath); expect(jsonData.entries.length).toBe(2); @@ -116,8 +120,7 @@ describe("GET /write", () => { }); it('the time is correct', () => { - const data = fs.readFileSync(filePath); - const jsonData = JSON.parse(data.toString()); + const jsonData = getData(filePath); const entry = jsonData.entries.at(-1) expect(entry.time.created).toBeGreaterThan(date.getTime()); @@ -138,8 +141,7 @@ describe("GET /write", () => { }); it('the distance is correct', () => { - const data = fs.readFileSync(filePath); - const jsonData = JSON.parse(data.toString()); + const jsonData = getData(filePath); const entry = jsonData.entries.at(-1) expect(entry.distance.horizontal).toBeCloseTo(1813.926); @@ -147,9 +149,15 @@ describe("GET /write", () => { expect(entry.distance.total).toBeCloseTo(2071.311); }); + it('the angle is correct', () => { + const jsonData = getData(filePath); + const entry = jsonData.entries.at(-1) + + expect(entry.angle).toBeCloseTo(83.795775); + }); + it('the speed is correct', () => { - const data = fs.readFileSync(filePath); - const jsonData = JSON.parse(data.toString()); + const jsonData = getData(filePath); const entry = jsonData.entries.at(-1) expect(isInRange(entry.speed.horizontal, 870, 10)).toBe(true); @@ -158,8 +166,7 @@ describe("GET /write", () => { }); it('check ignore', async () => { - let data = fs.readFileSync(filePath); - let jsonData = JSON.parse(data.toString()); + let jsonData = getData(filePath); let entry = jsonData.entries[1]; const lastEntry = jsonData.entries[0]; @@ -167,8 +174,7 @@ describe("GET /write", () => { expect(lastEntry.ignore).toBe(true); // last one to high hdop to be true await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); - data = fs.readFileSync(filePath); // rereading the data - jsonData = JSON.parse(data.toString()); + jsonData = getData(filePath); entry = jsonData.entries[1]; // same data point, but not last now therefore ignore true expect(entry.ignore).toBe(true); }); From 003e57d1ffd932bc9182d88fbb5f70e2882ccbec Mon Sep 17 00:00:00 2001 From: Type-Style Date: Thu, 1 Feb 2024 12:35:31 +0100 Subject: [PATCH 39/62] [change] #32 test to include optional leading 0 for days --- src/tests/write.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/tests/write.test.ts b/src/tests/write.test.ts index 14d030ce..69f85f27 100644 --- a/src/tests/write.test.ts +++ b/src/tests/write.test.ts @@ -27,7 +27,7 @@ async function callServer(timestamp = new Date().getTime(), query: string, expec } } -function getData(filePath:string) { +function getData(filePath: string) { const data = fs.readFileSync(filePath); return JSON.parse(data.toString()); } @@ -129,11 +129,10 @@ describe("GET /write", () => { const germanDayPattern = "(Montag|Dienstag|Mittwoch|Donnerstag|Freitag|Samstag|Sonntag)"; - const dayOfMonthPattern = "(0[1-9]|[12][0-9]|3[01])"; + const dayOfMonthPattern = "(0?[1-9]|[12][0-9]|3[01])"; const germanMonthPattern = "(Januar|Februar|März|April|Mai|Juni|Juli|August|September|Oktober|November|Dezember)"; const yearPattern = "(\\d{4})"; const timePattern = "([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]"; - const pattern = new RegExp(`^${germanDayPattern}, ${dayOfMonthPattern}. ${germanMonthPattern} ${yearPattern} um ${timePattern}$`); const string = entry.time.createdString; expect(pattern.test(string)).toBeTruthy(); @@ -179,4 +178,11 @@ describe("GET /write", () => { expect(entry.ignore).toBe(true); }); + /* it('can handle up to 500 lines', async () => { + for (let i = 0; i <= 500; i++) { + await callServer(undefined, `user=xx&lat=${52 + Math.random()}&lon=${13 + Math.random()}×tamp=R3Pl4C3&hdop=${25 * Math.random()}&altitude=${i}&speed=150.000&heading=${360 * Math.random()}&key=test`, 200, "GET"); + + } + }); */ + }); \ No newline at end of file From dbbbb4615bbfc6888d837d6cfe3d07874c318824 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Thu, 1 Feb 2024 15:05:22 +0100 Subject: [PATCH 40/62] [!!!Task] #18 add uncaughtExeption handler as last resort --- src/app.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app.ts b/src/app.ts index d65799a3..8dbc27d4 100644 --- a/src/app.ts +++ b/src/app.ts @@ -42,4 +42,10 @@ app.use(error.handler); // init server app.listen(80, () => { logger.log(`Server running //localhost:80, ENV: ${process.env.NODE_ENV}`, true); +}); + +process.on('uncaughtException', function(err) { + console.error('Caught exception:', err); + logger.error(err); + process.exit(1); }); \ No newline at end of file From 50c97004ff1b22132507d6d3751e026477777ea3 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 2 Feb 2024 13:17:35 +0100 Subject: [PATCH 41/62] [Task] #7 enhance static options to include common filetypes; index file start is used as index file to avoid collisions with host provider --- src/app.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/app.ts b/src/app.ts index 8dbc27d4..d7fc1615 100644 --- a/src/app.ts +++ b/src/app.ts @@ -18,9 +18,9 @@ app.use( directives: { "default-src": "'self'", "img-src": "*" - }, - }, - }), + } + } + }) ); app.use(hpp()); @@ -33,7 +33,10 @@ app.get('/', (req, res) => { app.use('/write', writeRouter); // use httpdocs as static folder -app.use('/', express.static(path.join(__dirname, 'httpdocs'))) +app.use('/', express.static(path.join(__dirname, 'httpdocs'), { + extensions: ['html', 'txt', "pdf"], + index: "start.html", +})) // error handling app.use(error.notFound); From 04e276175b9441eebdef94ad1a2e4d57c1f30d04 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 2 Feb 2024 13:36:00 +0100 Subject: [PATCH 42/62] [change] #32 validation to be used more explictly --- src/controller/write.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/controller/write.ts b/src/controller/write.ts index e4c6ef05..dc0658d2 100644 --- a/src/controller/write.ts +++ b/src/controller/write.ts @@ -36,9 +36,7 @@ async function errorChecking (req:Request, res:Response, next:NextFunction) { const router = express.Router(); -router.use(entry.validate); - -router.get('/', errorChecking); -router.head('/', errorChecking); +router.get('/', entry.validate, errorChecking); +router.head('/', entry.validate, errorChecking); export default router; \ No newline at end of file From c0b9db6ff60cca7040ec28651981f6dcefda59cc Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 2 Feb 2024 13:36:30 +0100 Subject: [PATCH 43/62] [change] #32 add index to log while writing --- src/scripts/file.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/file.ts b/src/scripts/file.ts index 5f0cbe5d..5fdec3fd 100644 --- a/src/scripts/file.ts +++ b/src/scripts/file.ts @@ -53,7 +53,7 @@ export const write = (res:Response, fileObj:File.Obj, next: NextFunction) => { const content = JSON.stringify(fileObj.content, undefined, 2); fs.writeFileSync(fileObj.path, content); fileObj.content = JSON.parse(content); - logger.log(`written to file: ${fileObj.path}`); + logger.log(`written to file: ${fileObj.path} ${fileObj.content ? fileObj.content?.entries.length - 1 : ''}`); } catch (err) { createError(res, 500, `File (${fileObj.path}) cannot be written to`, next); } From 29cc03c0f6fb158190d0e2252d19f6fec937472d Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 2 Feb 2024 13:37:49 +0100 Subject: [PATCH 44/62] [Task] #32 test if 1000 calls can be made with randomized data --- src/tests/write.test.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/tests/write.test.ts b/src/tests/write.test.ts index 69f85f27..e9044669 100644 --- a/src/tests/write.test.ts +++ b/src/tests/write.test.ts @@ -161,7 +161,7 @@ describe("GET /write", () => { expect(isInRange(entry.speed.horizontal, 870, 10)).toBe(true); expect(isInRange(entry.speed.vertical, -478, 10)).toBe(true); - expect(isInRange(entry.speed.total, 992, 10)).toBe(true); + expect(isInRange(entry.speed.total, 992, 15)).toBe(true); }); it('check ignore', async () => { @@ -176,13 +176,17 @@ describe("GET /write", () => { jsonData = getData(filePath); entry = jsonData.entries[1]; // same data point, but not last now therefore ignore true expect(entry.ignore).toBe(true); - }); - - /* it('can handle up to 500 lines', async () => { - for (let i = 0; i <= 500; i++) { - await callServer(undefined, `user=xx&lat=${52 + Math.random()}&lon=${13 + Math.random()}×tamp=R3Pl4C3&hdop=${25 * Math.random()}&altitude=${i}&speed=150.000&heading=${360 * Math.random()}&key=test`, 200, "GET"); + }); +}); +describe('API calls', () => { + test(`1000 api calls`, async () => { + for (let i = 0; i < 1000; i++) { + const url = `http://localhost:80/write?user=xx&lat=${(52 + Math.random()).toFixed(3)}&lon=${(13 + Math.random()).toFixed(3)}×tamp=${new Date().getTime()}&hdop=${(25 * Math.random()).toFixed(3)}&altitude=${i}&speed=88.888&heading=${(360 * Math.random()).toFixed(3)}&key=test`; + const response = await axios.get(url); + expect(response.status).toBe(200); } - }); */ + }, 20000); // adjust this to to fit your setup + }); \ No newline at end of file From 1145e9e85c2aef53671cf431a1deb8b79d537b20 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 2 Feb 2024 13:39:01 +0100 Subject: [PATCH 45/62] [!!! Task] #32 limit JSON Data to be 1000 lines: replace last line with most recent entry --- src/models/entry.ts | 10 ++++++++-- src/tests/write.test.ts | 8 ++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/models/entry.ts b/src/models/entry.ts index 52d53953..7bee7c89 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -7,6 +7,7 @@ import { getTime } from '@src/scripts/time'; import { getSpeed } from '@src/scripts/speed'; import { getDistance } from '@src/scripts/distance'; import { getAngle } from '@src/scripts/angle'; +import logger from '@src/scripts/logger'; export const entry = { @@ -42,8 +43,13 @@ export const entry = { entry.speed = getSpeed(Number(req.query.speed)) } - - entries.push(entry); + if (entries.length >= 1000) { + logger.log(`File over 1000 lines: ${fileObj.path}`); + entries[entries.length - 1] = entry; // replace last entry + } else { + entries.push(entry); + } + file.write(res, fileObj, next); diff --git a/src/tests/write.test.ts b/src/tests/write.test.ts index e9044669..5db4a878 100644 --- a/src/tests/write.test.ts +++ b/src/tests/write.test.ts @@ -188,5 +188,13 @@ describe('API calls', () => { } }, 20000); // adjust this to to fit your setup + test(`length of json should not exceed 1000`, async () => { + const date = new Date(); + const formattedDate = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + const dirPath = path.resolve(__dirname, '../../dist/data/'); + const filePath = path.resolve(dirPath, `data-${formattedDate}.json`); + const jsonData = getData(filePath); + expect(jsonData.entries.length).toBeLessThanOrEqual(1000); + }); }); \ No newline at end of file From 19aa8eb522b2a10ea151582a1dee9c49af296dbf Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 2 Feb 2024 14:01:42 +0100 Subject: [PATCH 46/62] [Change, Task] #32 if 1000 entries exceeded, only replace last if hdop is good --- src/models/entry.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/models/entry.ts b/src/models/entry.ts index 7bee7c89..147256e6 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -45,11 +45,12 @@ export const entry = { if (entries.length >= 1000) { logger.log(`File over 1000 lines: ${fileObj.path}`); - entries[entries.length - 1] = entry; // replace last entry + if (entry.hdop < 12 || (lastEntry && entry.hdop < lastEntry.hdop)) { + entries[entries.length - 1] = entry; // replace last entry + } } else { entries.push(entry); - } - + } file.write(res, fileObj, next); From e1d6827aa86748759d2673dc137c54153ae60347 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 2 Feb 2024 15:07:07 +0100 Subject: [PATCH 47/62] [Change] build action enable button to on manually --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4d0f1e0c..72c45e33 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ name: Node.js CI on: + workflow_dispatch: push: branches: [ "main", "dev" ] pull_request: From df0711ce64f4d7e72798a62ff5c1ed5622443250 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Fri, 2 Feb 2024 14:25:04 +0100 Subject: [PATCH 48/62] [temp] test y tests fail --- .github/workflows/build.yml | 2 +- package.json | 2 +- src/app.ts | 26 +++-------------------- src/tests/app.test.ts | 2 +- src/tests/write.test.ts | 41 ++++++++++++++++++------------------- 5 files changed, 26 insertions(+), 47 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4d0f1e0c..f92eb6da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,5 +21,5 @@ jobs: - name: Start server run: | npm start & - sleep 5 # Give server some time to start + sleep 10 # Give server some time to start - run: npm test diff --git a/package.json b/package.json index 7e450e91..86d5cdc4 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "start": "node dist/app.js", "dev": "rm -rf dist/* && cp -R httpdocs/ dist/ && nodemon src/app.ts", "lint": "eslint . --fix", - "test": "jest" + "test": "jest --runInBand" }, "keywords": [], "author": "Type-Style", diff --git a/src/app.ts b/src/app.ts index d7fc1615..382c2550 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,7 +1,6 @@ require('module-alias/register'); import { config } from 'dotenv'; import express from 'express'; -import helmet from 'helmet'; import hpp from 'hpp'; import cache from './cache'; import * as error from "./error"; @@ -9,20 +8,10 @@ import writeRouter from '@src/controller/write'; import path from 'path'; import logger from '@src/scripts/logger'; + // configurations config(); const app = express(); -app.use( - helmet({ - contentSecurityPolicy: { - directives: { - "default-src": "'self'", - "img-src": "*" - } - } - }) -); - app.use(hpp()); app.use(cache); @@ -33,10 +22,7 @@ app.get('/', (req, res) => { app.use('/write', writeRouter); // use httpdocs as static folder -app.use('/', express.static(path.join(__dirname, 'httpdocs'), { - extensions: ['html', 'txt', "pdf"], - index: "start.html", -})) +app.use('/', express.static(path.join(__dirname, 'httpdocs'))) // error handling app.use(error.notFound); @@ -44,11 +30,5 @@ app.use(error.handler); // init server app.listen(80, () => { - logger.log(`Server running //localhost:80, ENV: ${process.env.NODE_ENV}`, true); -}); - -process.on('uncaughtException', function(err) { - console.error('Caught exception:', err); - logger.error(err); - process.exit(1); + logger.log(`Server running //localhost:80`); }); \ No newline at end of file diff --git a/src/tests/app.test.ts b/src/tests/app.test.ts index 453caf40..b7c01c25 100644 --- a/src/tests/app.test.ts +++ b/src/tests/app.test.ts @@ -4,7 +4,7 @@ describe('Server Status', () => { it('The server is running', async () => { let serverStatus; try { - const response = await axios.get('http://localhost:80'); + const response = await axios.get('http://localhost:80/'); serverStatus = response.status; } catch (error) { console.error(error); diff --git a/src/tests/write.test.ts b/src/tests/write.test.ts index 5db4a878..e5b4a29f 100644 --- a/src/tests/write.test.ts +++ b/src/tests/write.test.ts @@ -1,6 +1,6 @@ import axios, { AxiosError } from 'axios'; import fs from "fs"; -import path from "path"; +// import path from "path"; async function callServer(timestamp = new Date().getTime(), query: string, expectStatus: number = 200, method: string = "HEAD") { const url = new URL("http://localhost:80/write?"); @@ -27,59 +27,59 @@ async function callServer(timestamp = new Date().getTime(), query: string, expec } } -function getData(filePath: string) { +/* function getData(filePath: string) { const data = fs.readFileSync(filePath); return JSON.parse(data.toString()); } function isInRange(actual: string | number, expected: number, range: number) { return Math.abs(Number(actual) - expected) <= range; -} +} */ describe('HEAD /write', () => { it('with all parameters correctly set it should succeed', async () => { - callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200); + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200); }); it('without key it sends 403', async () => { - callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0", 403); + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0", 403); }); it('with user length not equal to 2 it sends 422', async () => { - callServer(undefined, "user=x&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + await callServer(undefined, "user=x&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); }); it('with lat not between -90 and 90 it sends 422', async () => { - callServer(undefined, "user=xx&lat=91.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + await callServer(undefined, "user=xx&lat=91.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); }); it('with lon not between -180 and 180 it sends 422', async () => { - callServer(undefined, "user=xx&lat=45.000&lon=181.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + await callServer(undefined, "user=xx&lat=45.000&lon=181.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); }); it('with timestamp to old sends 422', async () => { const timestamp = new Date().getTime() - 24 * 60 * 60 * 1000 * 2; // two days ago - callServer(timestamp, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + await callServer(timestamp, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); }) it('with hdop not between 0 and 100 it sends 422', async () => { - callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); }); it('with altitude not between 0 and 10000 it sends 422', async () => { - callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test", 422); + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test", 422); }); it('with speed not between 0 and 300 it sends 422', async () => { - callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test", 422); + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test", 422); }); it('with heading not between 0 and 360 it sends 422', async () => { - callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test", 422); + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test", 422); }); }); - +/* describe("GET /write", () => { const date = new Date(); const formattedDate = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; @@ -87,7 +87,7 @@ describe("GET /write", () => { const filePath = path.resolve(dirPath, `data-${formattedDate}.json`); it('there should a file of the current date', async () => { - await callServer(undefined, "user=xx&lat=52.51451&lon=13.35105×tamp=R3Pl4C3&hdop=20.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + await await callServer(undefined, "user=xx&lat=52.51451&lon=13.35105×tamp=R3Pl4C3&hdop=20.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); fs.access(filePath, fs.constants.F_OK, (err) => { expect(err).toBeFalsy(); @@ -109,7 +109,7 @@ describe("GET /write", () => { return new Promise(done => { // Increase the timeout for this test setTimeout(async () => { - await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + await await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); const jsonData = getData(filePath); expect(jsonData.entries.length).toBe(2); @@ -172,14 +172,14 @@ describe("GET /write", () => { expect(entry.ignore).toBe(false); // current one to be false allways expect(lastEntry.ignore).toBe(true); // last one to high hdop to be true - await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + await await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); jsonData = getData(filePath); entry = jsonData.entries[1]; // same data point, but not last now therefore ignore true expect(entry.ignore).toBe(true); }); -}); +}); */ -describe('API calls', () => { +/* describe('API calls', () => { test(`1000 api calls`, async () => { for (let i = 0; i < 1000; i++) { const url = `http://localhost:80/write?user=xx&lat=${(52 + Math.random()).toFixed(3)}&lon=${(13 + Math.random()).toFixed(3)}×tamp=${new Date().getTime()}&hdop=${(25 * Math.random()).toFixed(3)}&altitude=${i}&speed=88.888&heading=${(360 * Math.random()).toFixed(3)}&key=test`; @@ -196,5 +196,4 @@ describe('API calls', () => { const jsonData = getData(filePath); expect(jsonData.entries.length).toBeLessThanOrEqual(1000); }); - -}); \ No newline at end of file +}); */ \ No newline at end of file From 9702002fe5e4214ffbb95d604b469e2c28cdd5bb Mon Sep 17 00:00:00 2001 From: Type-Style Date: Mon, 5 Feb 2024 14:18:36 +0100 Subject: [PATCH 49/62] Create node.js.yml --- .github/workflows/node.js.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/node.js.yml diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 00000000..a38f85eb --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,34 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs + +name: Node (2) + +on: + workflow_dispatch: + push: + branches: [ "dev" ] + pull_request: + branches: [ "dev" ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run build --if-present + - name: Start server + run: | + npm start & + sleep 6 # Give server some time to start + - run: npm test From 32d5579bafee315af564883e9aeb14898bc432a0 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Mon, 5 Feb 2024 14:38:28 +0100 Subject: [PATCH 50/62] Create main.yml --- .github/workflows/main.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..b00a8292 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,31 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs + +name: Node3 + +on: + workflow_dispatch: + push: + branches: [ "dev", "main" ] + pull_request: + branches: [ "dev", "main" ] + +jobs: + build: + runs-on: ubuntu-latest + + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: '20', + cache: 'npm' + - run: npm ci + - run: npm run build --if-present + - name: Start server + run: | + npm start & + sleep 6 # Give server some time to start + - run: npm test From 0ca2a85fd4ba3813c3df906e26ea01754840e2de Mon Sep 17 00:00:00 2001 From: Type-Style Date: Mon, 5 Feb 2024 14:40:31 +0100 Subject: [PATCH 51/62] [!!!Fix] Created new workflow to build / test node, commented tests back in. Increased time between server calls in test, to check difference time more accurately --- .github/workflows/main.yml | 13 ++++--- package.json | 2 +- src/tests/write.test.ts | 79 +++++++++++++++++++------------------- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b00a8292..a755f3f3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,3 @@ -# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs - name: Node3 on: @@ -20,12 +17,16 @@ jobs: - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: - node-version: '20', + node-version: '20' cache: 'npm' - run: npm ci - run: npm run build --if-present - name: Start server run: | - npm start & + sudo npm start & sleep 6 # Give server some time to start - - run: npm test + - name: Check if server is running + run: | + curl --fail http://localhost:80 || exit 1 + - name: Run tests + run: npm run test \ No newline at end of file diff --git a/package.json b/package.json index 227ed1f3..7e450e91 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "start": "node dist/app.js", "dev": "rm -rf dist/* && cp -R httpdocs/ dist/ && nodemon src/app.ts", "lint": "eslint . --fix", - "test": "jest --maxWorkers=1" + "test": "jest" }, "keywords": [], "author": "Type-Style", diff --git a/src/tests/write.test.ts b/src/tests/write.test.ts index 507781ef..b7bd9bc3 100644 --- a/src/tests/write.test.ts +++ b/src/tests/write.test.ts @@ -1,6 +1,6 @@ import axios, { AxiosError } from 'axios'; -//import fs from "fs"; -// import path from "path"; +import fs from "fs"; +import path from "path"; async function callServer(timestamp = new Date().getTime(), query: string, expectStatus: number = 200, method: string = "HEAD") { const url = new URL("http://localhost:80/write?"); @@ -32,59 +32,59 @@ async function callServer(timestamp = new Date().getTime(), query: string, expec } - /* function getData(filePath: string) { + function getData(filePath: string) { const data = fs.readFileSync(filePath); return JSON.parse(data.toString()); } function isInRange(actual: string | number, expected: number, range: number) { return Math.abs(Number(actual) - expected) <= range; - } */ + } describe('HEAD /write', () => { it('with all parameters correctly set it should succeed', async () => { await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200); }); - // it('without key it sends 403', async () => { - // await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0", 403); - // }); + it('without key it sends 403', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0", 403); + }); it('with user length not equal to 2 it sends 422', async () => { await callServer(undefined, "user=x&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); }); - // it('with lat not between -90 and 90 it sends 422', async () => { - // await callServer(undefined, "user=xx&lat=91.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); - // }); + it('with lat not between -90 and 90 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=91.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); - // it('with lon not between -180 and 180 it sends 422', async () => { - // await callServer(undefined, "user=xx&lat=45.000&lon=181.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); - // }); + it('with lon not between -180 and 180 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=181.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); - // it('with timestamp to old sends 422', async () => { - // const timestamp = new Date().getTime() - 24 * 60 * 60 * 1000 * 2; // two days ago - // await callServer(timestamp, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); - // }) + it('with timestamp to old sends 422', async () => { + const timestamp = new Date().getTime() - 24 * 60 * 60 * 1000 * 2; // two days ago + await callServer(timestamp, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }) - // it('with hdop not between 0 and 100 it sends 422', async () => { - // await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); - // }); + it('with hdop not between 0 and 100 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); - // it('with altitude not between 0 and 10000 it sends 422', async () => { - // await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test", 422); - // }); + it('with altitude not between 0 and 10000 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test", 422); + }); - // it('with speed not between 0 and 300 it sends 422', async () => { - // await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test", 422); - // }); + it('with speed not between 0 and 300 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test", 422); + }); - // it('with heading not between 0 and 360 it sends 422', async () => { - // await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test", 422); - // }); + it('with heading not between 0 and 360 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test", 422); + }); }); -/* + describe("GET /write", () => { const date = new Date(); const formattedDate = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; @@ -112,7 +112,6 @@ describe("GET /write", () => { it('after second call and the JSON entries length is 2', () => { return new Promise(done => { - // Increase the timeout for this test setTimeout(async () => { await await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); const jsonData = getData(filePath); @@ -120,7 +119,7 @@ describe("GET /write", () => { expect(jsonData.entries.length).toBe(2); done(); - }, 2000); + }, 3500); }) }); @@ -129,8 +128,8 @@ describe("GET /write", () => { const entry = jsonData.entries.at(-1) expect(entry.time.created).toBeGreaterThan(date.getTime()); - expect(entry.time.diff).toBeGreaterThan(2); - expect(entry.time.diff).toBeLessThan(3); + expect(entry.time.diff).toBeGreaterThan(3.5); + expect(entry.time.diff).toBeLessThan(4); const germanDayPattern = "(Montag|Dienstag|Mittwoch|Donnerstag|Freitag|Samstag|Sonntag)"; @@ -164,9 +163,9 @@ describe("GET /write", () => { const jsonData = getData(filePath); const entry = jsonData.entries.at(-1) - expect(isInRange(entry.speed.horizontal, 870, 10)).toBe(true); - expect(isInRange(entry.speed.vertical, -478, 10)).toBe(true); - expect(isInRange(entry.speed.total, 992, 15)).toBe(true); + expect(isInRange(entry.speed.horizontal, 515, 10)).toBe(true); + expect(isInRange(entry.speed.vertical, -284, 10)).toBe(true); + expect(isInRange(entry.speed.total, 588, 15)).toBe(true); }); it('check ignore', async () => { @@ -182,9 +181,9 @@ describe("GET /write", () => { entry = jsonData.entries[1]; // same data point, but not last now therefore ignore true expect(entry.ignore).toBe(true); }); -}); */ +}); -/* describe('API calls', () => { +describe('API calls', () => { test(`1000 api calls`, async () => { for (let i = 0; i < 1000; i++) { const url = `http://localhost:80/write?user=xx&lat=${(52 + Math.random()).toFixed(3)}&lon=${(13 + Math.random()).toFixed(3)}×tamp=${new Date().getTime()}&hdop=${(25 * Math.random()).toFixed(3)}&altitude=${i}&speed=88.888&heading=${(360 * Math.random()).toFixed(3)}&key=test`; @@ -201,4 +200,4 @@ describe("GET /write", () => { const jsonData = getData(filePath); expect(jsonData.entries.length).toBeLessThanOrEqual(1000); }); -}); */ \ No newline at end of file +}); \ No newline at end of file From 8fb105755fdb9c1ff718369dd1f5fde341245383 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 6 Feb 2024 15:03:24 +0100 Subject: [PATCH 52/62] [Task] #33 moved ignore to its own file since it creates data rather than validating it --- src/models/entry.ts | 17 ++--------------- src/scripts/ignore.ts | 13 +++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) create mode 100644 src/scripts/ignore.ts diff --git a/src/models/entry.ts b/src/models/entry.ts index 147256e6..4305fea3 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -7,6 +7,7 @@ import { getTime } from '@src/scripts/time'; import { getSpeed } from '@src/scripts/speed'; import { getDistance } from '@src/scripts/distance'; import { getAngle } from '@src/scripts/angle'; +import { getIgnore } from '@src/scripts/ignore'; import logger from '@src/scripts/logger'; @@ -33,7 +34,7 @@ export const entry = { if (lastEntry) { // so there is a previous entry entry.time = getTime(Number(req.query.timestamp), lastEntry); - lastEntry.ignore = checkIgnore(lastEntry, entry); + lastEntry.ignore = getIgnore(lastEntry, entry); entry.angle = getAngle(lastEntry, entry); entry.distance = getDistance(entry, lastEntry) entry.speed = getSpeed(Number(req.query.speed), entry); @@ -115,20 +116,6 @@ export function checkTime(value: string) { return true } -function checkIgnore(lastEntry: Models.IEntry, entry: Models.IEntry): boolean { - let threshold = 6; // hdop not allowed to be higher - const maxThreshold = 25; - - const timing = Math.max(lastEntry.time.diff, entry.time.diff) - - // Threshold increases with older previous entries or farther future entries. - if (timing > 32) { - threshold += Math.min(lastEntry.time.diff / 60, maxThreshold); - } - - return lastEntry.hdop > threshold; -} - function checkKey(value: string) { if (process.env.NODE_ENV != "production" && value == "test") { diff --git a/src/scripts/ignore.ts b/src/scripts/ignore.ts new file mode 100644 index 00000000..3e928575 --- /dev/null +++ b/src/scripts/ignore.ts @@ -0,0 +1,13 @@ +export function getIgnore(lastEntry: Models.IEntry, entry: Models.IEntry): boolean { + let threshold = 6; // hdop not allowed to be higher + const maxThreshold = 25; + + const timing = Math.max(lastEntry.time.diff, entry.time.diff) + + // Threshold increases with older previous entries or farther future entries. + if (timing > 32) { + threshold += Math.min(lastEntry.time.diff / 60, maxThreshold); + } + + return lastEntry.hdop > threshold; +} \ No newline at end of file From 5ddf95158447761743e0b29bd425a85c0c292218 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 6 Feb 2024 23:43:29 +0100 Subject: [PATCH 53/62] 42 output json (#44) * [Task] #42, created route to output json * [Task] #42 added tests for read json --- jest.config.js | 1 + src/app.ts | 7 +- src/controller/read.ts | 34 +++++ .../{write.test.ts => integration.test.ts} | 121 ++++++++++++------ 4 files changed, 117 insertions(+), 46 deletions(-) create mode 100644 src/controller/read.ts rename src/tests/{write.test.ts => integration.test.ts} (59%) diff --git a/jest.config.js b/jest.config.js index 2adbbae4..0a57f547 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,4 +6,5 @@ module.exports = { moduleNameMapper: { '^@src/(.*)$': '/src/$1', }, + bail: true }; \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 0aaaa42a..0f5b50a1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,6 +6,7 @@ import hpp from 'hpp'; import cache from './cache'; import * as error from "./error"; import writeRouter from '@src/controller/write'; +import readRouter from '@src/controller/read'; import path from 'path'; import logger from '@src/scripts/logger'; @@ -31,11 +32,9 @@ app.get('/', (req, res) => { res.send('Hello World, via TypeScript and Node.js!'); }); -app.get('/test', (req, res) => { - process.exit(1); - res.send('Hello World 2, via TypeScript and Node.js!'); -}); + app.use('/write', writeRouter); +app.use('/read', readRouter); // use httpdocs as static folder app.use('/', express.static(path.join(__dirname, 'httpdocs'), { diff --git a/src/controller/read.ts b/src/controller/read.ts new file mode 100644 index 00000000..f17c93c9 --- /dev/null +++ b/src/controller/read.ts @@ -0,0 +1,34 @@ +import express, { Request, Response, NextFunction } from 'express'; +import * as file from '@src/scripts/file'; +import { create as createError } from '@src/error'; +import { validationResult, query } from 'express-validator'; + +const router = express.Router(); + +router.get('/', + [query('index').isInt().withMessage("not an integer") + .isLength({ max: 3 }).withMessage("not in range") + .toInt()], + async function getRead(req:Request, res:Response, next:NextFunction) { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return createError(res, 400, JSON.stringify({errors: errors.array()}), next) + } + + const fileObj: File.Obj = file.getFile(res, next); + fileObj.content = await file.readAsJson(res, fileObj.path, next) + if (!fileObj.content || !Array.isArray(fileObj.content.entries)) { + return createError(res, undefined, `File corrupt: ${fileObj.path}`, next); + } + + let entries = fileObj.content.entries; + + if (req.query.index) { + entries = entries.slice(Number(req.query.index)); + } + + res.json({entries}); +}); + + +export default router; \ No newline at end of file diff --git a/src/tests/write.test.ts b/src/tests/integration.test.ts similarity index 59% rename from src/tests/write.test.ts rename to src/tests/integration.test.ts index b7bd9bc3..2d40ced0 100644 --- a/src/tests/write.test.ts +++ b/src/tests/integration.test.ts @@ -32,57 +32,57 @@ async function callServer(timestamp = new Date().getTime(), query: string, expec } - function getData(filePath: string) { - const data = fs.readFileSync(filePath); - return JSON.parse(data.toString()); - } - - function isInRange(actual: string | number, expected: number, range: number) { - return Math.abs(Number(actual) - expected) <= range; - } +function getData(filePath: string) { + const data = fs.readFileSync(filePath); + return JSON.parse(data.toString()); +} + +function isInRange(actual: string | number, expected: number, range: number) { + return Math.abs(Number(actual) - expected) <= range; +} - describe('HEAD /write', () => { - it('with all parameters correctly set it should succeed', async () => { - await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200); - }); +describe('HEAD /write', () => { + it('with all parameters correctly set it should succeed', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200); + }); - it('without key it sends 403', async () => { - await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0", 403); - }); + it('without key it sends 403', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0", 403); + }); - it('with user length not equal to 2 it sends 422', async () => { - await callServer(undefined, "user=x&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); - }); + it('with user length not equal to 2 it sends 422', async () => { + await callServer(undefined, "user=x&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); - it('with lat not between -90 and 90 it sends 422', async () => { - await callServer(undefined, "user=xx&lat=91.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); - }); + it('with lat not between -90 and 90 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=91.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); - it('with lon not between -180 and 180 it sends 422', async () => { - await callServer(undefined, "user=xx&lat=45.000&lon=181.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); - }); + it('with lon not between -180 and 180 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=181.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); - it('with timestamp to old sends 422', async () => { - const timestamp = new Date().getTime() - 24 * 60 * 60 * 1000 * 2; // two days ago - await callServer(timestamp, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); - }) + it('with timestamp to old sends 422', async () => { + const timestamp = new Date().getTime() - 24 * 60 * 60 * 1000 * 2; // two days ago + await callServer(timestamp, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }) - it('with hdop not between 0 and 100 it sends 422', async () => { - await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); - }); + it('with hdop not between 0 and 100 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); - it('with altitude not between 0 and 10000 it sends 422', async () => { - await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test", 422); - }); + it('with altitude not between 0 and 10000 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test", 422); + }); - it('with speed not between 0 and 300 it sends 422', async () => { - await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test", 422); - }); + it('with speed not between 0 and 300 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test", 422); + }); - it('with heading not between 0 and 360 it sends 422', async () => { - await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test", 422); - }); - }); + it('with heading not between 0 and 360 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test", 422); + }); +}); describe("GET /write", () => { @@ -180,7 +180,7 @@ describe("GET /write", () => { jsonData = getData(filePath); entry = jsonData.entries[1]; // same data point, but not last now therefore ignore true expect(entry.ignore).toBe(true); - }); + }); }); describe('API calls', () => { @@ -200,4 +200,41 @@ describe('API calls', () => { const jsonData = getData(filePath); expect(jsonData.entries.length).toBeLessThanOrEqual(1000); }); +}); + + +describe('/read', () => { + test(`returns json`, async () => { + const response = await axios.get("http://localhost:80/read?index=0"); + expect(response.status).toBe(200); + expect(response.headers['content-type']).toEqual(expect.stringContaining('application/json')); + }); + test(`index parameter to long`, async () => { + try { + await axios.get("http://localhost:80/read?index=1234"); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(400); + } else { + console.error(axiosError); + } + } + }); + test(`index parameter to be a number`, async () => { + try { + await axios.get("http://localhost:80/read?index=a9"); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(400); + } else { + console.error(axiosError); + } + } + }); + test(`index parameter reduces length of json`, async () => { + const response = await axios.get("http://localhost:80/read?index=999"); + expect(response.data.entries.length).toBe(1); + }); }); \ No newline at end of file From f1a628c79f193fe9b6d7d631b7cbccfb227e6818 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 13 Feb 2024 19:01:19 +0100 Subject: [PATCH 54/62] 41 add rate limiter (#45) * [Task] #18, limit request size for security reasons * [Task] #43, introduce gzip to transfer data * [Task] #34 improve error handling, log server shutdowns * [Task] #34 installed and integrated tooBusy to send 503 when load is high * [Task] #34 improved tooBusy, improved formatting * [Task, Temp] #41 installed ratelimiter and slowDown * [Task] #42 cleanup ipv6 addresses * [Change] #10 error handling for better gitBash and txt output, also reduced stack in case of validation errors * [Task] #41 prepare Log for RateLImit errors * [Temp] #41 write route rateLImited temp: see Todos * [Task] #34 colorize prefix in console * [Task] #42 extract middlewares and move to folder * [Task] #41 ratelimiter cleaning up periodicly * [Task] #41 skip tests in rateLimiting --- .github/workflows/main.yml | 2 +- package-lock.json | 129 ++++++++++++++++++++++++++++++++-- package.json | 9 ++- src/app.ts | 103 +++++++++++++++++++++------ src/controller/read.ts | 2 +- src/controller/write.ts | 39 +++++----- src/{ => middleware}/cache.ts | 0 src/{ => middleware}/error.ts | 2 +- src/middleware/limit.ts | 59 ++++++++++++++++ src/models/entry.ts | 2 +- src/scripts/file.ts | 2 +- src/scripts/logger.ts | 44 +++++++++--- src/tests/integration.test.ts | 2 +- types.d.ts | 9 +++ 14 files changed, 347 insertions(+), 57 deletions(-) rename src/{ => middleware}/cache.ts (100%) rename src/{ => middleware}/error.ts (96%) create mode 100644 src/middleware/limit.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a755f3f3..5eaaa852 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: - name: Start server run: | sudo npm start & - sleep 6 # Give server some time to start + sleep 8 # Give server some time to start - name: Check if server is running run: | curl --fail http://localhost:80 || exit 1 diff --git a/package-lock.json b/package-lock.json index 4c0b3553..085305f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,21 +9,28 @@ "version": "0.0.1", "dependencies": { "chalk": "^4.1.2", + "compression": "^1.7.4", "express": "^4.18.2", + "express-rate-limit": "^7.1.5", + "express-slow-down": "^2.0.1", "express-validator": "^7.0.1", "helmet": "^7.1.0", "hpp": "^0.2.3", - "module-alias": "^2.2.3" + "module-alias": "^2.2.3", + "raw-body": "^2.5.2", + "toobusy-js": "^0.5.1" }, "devDependencies": { "@jest/globals": "^29.7.0", "@tsconfig/node20": "^20.1.2", "@types/bcrypt": "^5.0.2", + "@types/compression": "^1.7.5", "@types/dotenv": "^8.2.0", "@types/express": "^4.17.21", "@types/hpp": "^0.2.5", "@types/jest": "^29.5.11", "@types/node": "^20.10.6", + "@types/toobusy-js": "^0.5.4", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", "axios": "^1.6.5", @@ -1513,6 +1520,15 @@ "@types/node": "*" } }, + "node_modules/@types/compression": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.5.tgz", + "integrity": "sha512-AAQvK5pxMpaT+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -1680,6 +1696,12 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/toobusy-js": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@types/toobusy-js/-/toobusy-js-0.5.4.tgz", + "integrity": "sha512-hsKMbYiaL3ZWx7B3FYyN0rEJexw7I1HgKbNToX3ZZJv6373to954wlA7zrXR3/XoVwZnFwWqFguBs91sNzJGKQ==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -2306,6 +2328,20 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2595,6 +2631,47 @@ "node": ">= 0.8" } }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3348,6 +3425,34 @@ "node": ">= 0.10.0" } }, + "node_modules/express-rate-limit": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.1.5.tgz", + "integrity": "sha512-/iVogxu7ueadrepw1bS0X0kaRC/U0afwiYRSLg68Ts+p4Dc85Q5QKsOnPS/QUjPMHvOJQtBDrZgvkOzf8ejUYw==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "4 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/express-slow-down": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/express-slow-down/-/express-slow-down-2.0.1.tgz", + "integrity": "sha512-zRogSZhNXJYKDBekhgFfFXGrOngH7Fub7Mx2g8OQ4RUBwSJP/3TVEKMgSGR/WlneT0mJ6NBUnidHhIELGVPe3w==", + "dependencies": { + "express-rate-limit": "7" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "express": ">= 4" + } + }, "node_modules/express-validator": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.0.1.tgz", @@ -5122,6 +5227,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5518,9 +5631,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -6031,6 +6144,14 @@ "node": ">=0.6" } }, + "node_modules/toobusy-js": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/toobusy-js/-/toobusy-js-0.5.1.tgz", + "integrity": "sha512-GiCux/c8G2TV0FTDgtxnXOxmSAndaI/9b1YxT14CqyeBDtTZAcJLx9KlXT3qECi8D0XCc78T4sN/7gWtjRyCaA==", + "engines": { + "node": ">=0.9.1" + } + }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", diff --git a/package.json b/package.json index 7e450e91..8b597505 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,13 @@ "@jest/globals": "^29.7.0", "@tsconfig/node20": "^20.1.2", "@types/bcrypt": "^5.0.2", + "@types/compression": "^1.7.5", "@types/dotenv": "^8.2.0", "@types/express": "^4.17.21", "@types/hpp": "^0.2.5", "@types/jest": "^29.5.11", "@types/node": "^20.10.6", + "@types/toobusy-js": "^0.5.4", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", "axios": "^1.6.5", @@ -37,11 +39,16 @@ }, "dependencies": { "chalk": "^4.1.2", + "compression": "^1.7.4", "express": "^4.18.2", + "express-rate-limit": "^7.1.5", + "express-slow-down": "^2.0.1", "express-validator": "^7.0.1", "helmet": "^7.1.0", "hpp": "^0.2.3", - "module-alias": "^2.2.3" + "module-alias": "^2.2.3", + "raw-body": "^2.5.2", + "toobusy-js": "^0.5.1" }, "_moduleAliases": { "@src": "dist" diff --git a/src/app.ts b/src/app.ts index 0f5b50a1..1e59d969 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,58 +1,117 @@ require('module-alias/register'); import { config } from 'dotenv'; import express from 'express'; +import toobusy from 'toobusy-js'; +// import { rateLimit } from 'express-rate-limit'; +// import { slowDown } from 'express-slow-down'; +import compression from 'compression'; import helmet from 'helmet'; import hpp from 'hpp'; -import cache from './cache'; -import * as error from "./error"; +import getRawBody from 'raw-body'; +import cache from './middleware/cache'; +import * as error from "./middleware/error"; import writeRouter from '@src/controller/write'; import readRouter from '@src/controller/read'; -import path from 'path'; +import path from 'path'; import logger from '@src/scripts/logger'; +// console.log({ "status": 403, "name": "Error", "message": { "errors": [{ "type": "field", "msg": "Invalid value", "path": "user", "location": "query" }, { "type": "field", "msg": "is required", "path": "lat", "location": "query" }]}}); +// console.log(JSON.stringify({ "status": 403, "name": "Error", "message": { "errors": [{ "type": "field", "msg": "Invalid value", "path": "user", "location": "query" }, { "type": "field", "msg": "is required", "path": "lat", "location": "query" }]}}, null, 2)); + // configurations -config(); +config(); // dotenv + const app = express(); -app.use( - helmet({ - contentSecurityPolicy: { - directives: { - "default-src": "'self'", - "img-src": "*" - } - } - }) -); -app.use(hpp()); +app.use((req, res, next) => { // monitor eventloop to block requests if busy + if (toobusy()) { + res.status(503).set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Retry-After': '60' }).send("I'm busy right now, sorry."); + } else { next(); } +}); +app.use((req, res, next) => { // clean up IPv6 Addresses + if (req.ip) { + res.locals.ip = req.ip.startsWith('::ffff:') ? req.ip.substring(7) : req.ip; + next(); + } else { + const message = "No IP provided" + logger.error(message); + res.status(400).send(message); + } + +}) + +// const slowDownLimiter = slowDown({ +// windowMs: 1 * 60 * 1000, +// delayAfter: 5, // Allow 5 requests per 15 minutes. +// delayMs: (used) => (used - 5) * 1000, // Add delay after delayAfter is reached +// }) + +// const rateLimiter = rateLimit({ +// windowMs: 1 * 60 * 1000, +// limit: 10, // Limit each IP per `window` +// standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers +// legacyHeaders: false, // Disable the `X-RateLimit-*` headers +// }) + +app.use(helmet({ contentSecurityPolicy: { directives: { "default-src": "'self'", "img-src": "*" } } })); app.use(cache); +app.use(compression()) +app.use(hpp()); +app.use(function (req, res, next) { // limit request size limit when recieving data + if (!['POST', 'PUT', 'DELETE'].includes(req.method)) { return next(); } + getRawBody(req, { length: req.headers['content-length'], limit: '1mb', encoding: true }, + function (err) { + if (err) { return next(err) } + next() + } + ) +}) // routes app.get('/', (req, res) => { - res.send('Hello World, via TypeScript and Node.js!'); + console.log(req.ip + " - " + res.locals.ip); + res.send('Hello World, via TypeScript and Node.js! ' + res.locals.ip); }); - app.use('/write', writeRouter); app.use('/read', readRouter); // use httpdocs as static folder app.use('/', express.static(path.join(__dirname, 'httpdocs'), { extensions: ['html', 'txt', "pdf"], - index: "start.html", -})) + index: ["start.html", "start.txt"], +})); // error handling app.use(error.notFound); app.use(error.handler); // init server -app.listen(80, () => { - logger.log(`Server running //localhost:80, ENV: ${process.env.NODE_ENV}`, true); +const server = app.listen(80, () => { + logger.log(`Server running //localhost:80, ENV: ${process.env.NODE_ENV}`, true); +}); + +// catching shutdowns +['SIGINT', 'SIGTERM', 'exit'].forEach((signal) => { + process.on(signal, () => { + function logAndExit() { + // calling .shutdown allows your process to exit normally + toobusy.shutdown(); + logger.log(`Server shutdown on signal: ${signal} //localhost:80`, true); + process.exit(); + } + if (signal != "exit") { // give the server time to shutdown before closing + server.close(logAndExit); + } else { + logger.log(`Server shutdown immediate: ${signal} //localhost:80`, true); + } + }); }); -process.on('uncaughtException', function(err) { +// last resort error handling +process.on('uncaughtException', function (err) { console.error('Caught exception:', err); logger.error(err); + server.close(); process.exit(1); }); \ No newline at end of file diff --git a/src/controller/read.ts b/src/controller/read.ts index f17c93c9..02294dc6 100644 --- a/src/controller/read.ts +++ b/src/controller/read.ts @@ -1,6 +1,6 @@ import express, { Request, Response, NextFunction } from 'express'; import * as file from '@src/scripts/file'; -import { create as createError } from '@src/error'; +import { create as createError } from '@src/middleware/error'; import { validationResult, query } from 'express-validator'; const router = express.Router(); diff --git a/src/controller/write.ts b/src/controller/write.ts index dc0658d2..7b405ffd 100644 --- a/src/controller/write.ts +++ b/src/controller/write.ts @@ -1,19 +1,25 @@ import express, { Request, Response, NextFunction } from 'express'; import { entry } from '@src/models/entry'; import { validationResult } from 'express-validator'; -import { create as createError } from '@src/error'; - +import { create as createError } from '@src/middleware/error'; +import { baseSlowDown, errorRateLimiter } from '@src/middleware/limit'; // example call: /write?user=xx&lat=00.000&lon=00.000×tamp=1704063600000&hdop=0.0&altitude=0.000&speed=0.000&heading=000.0 -async function errorChecking (req:Request, res:Response, next:NextFunction) { + +function errorChecking(req: Request, res: Response, next: NextFunction) { const errors = validationResult(req); if (!errors.isEmpty()) { - const errorAsJson = { errors: errors.array()}; - const errorAsString = JSON.stringify(errorAsJson); - const hasKeyErrors = errors.array().some(error => error.msg.includes("Key")); - - // send forbidden or unprocessable content - return createError(res, hasKeyErrors ? 403 : 422, errorAsString, next) + // if errors happend, then rateLimit to prevent key bruteforcing + errorRateLimiter(req, res, () => { + const errorAsJson = { errors: errors.array() }; + const errorAsString = JSON.stringify(errorAsJson); + const hasKeyErrors = errors.array().some(error => error.msg.includes("Key")); + + // send forbidden or unprocessable content + return createError(res, hasKeyErrors ? 403 : 422, errorAsString, next) + }); + + return; } if (req.method == "HEAD") { @@ -21,22 +27,23 @@ async function errorChecking (req:Request, res:Response, next:NextFunction) { return; } + next(); +} + +async function writeData(req: Request, res: Response, next: NextFunction) { // Regular Save logic from here await entry.create(req, res, next); - if (!res.locals.error) { + if (!res.locals.error) { res.send(req.query); - } else { - /* at this point error handling already happend, - * or the request has already been send - * therefor there is no need for it again (only middleware to follow at this point) */ + } else { next(); } } const router = express.Router(); -router.get('/', entry.validate, errorChecking); -router.head('/', entry.validate, errorChecking); + router.get('/', baseSlowDown, entry.validate, errorChecking, writeData); +router.head('/', baseSlowDown, entry.validate, errorChecking); export default router; \ No newline at end of file diff --git a/src/cache.ts b/src/middleware/cache.ts similarity index 100% rename from src/cache.ts rename to src/middleware/cache.ts diff --git a/src/error.ts b/src/middleware/error.ts similarity index 96% rename from src/error.ts rename to src/middleware/error.ts index 7b8d624d..2eaf6e8d 100644 --- a/src/error.ts +++ b/src/middleware/error.ts @@ -29,7 +29,7 @@ export function handler(err: Error, req: Request, res: Response message = err.message; } - const responseBody = { + const responseBody:Response.Error = { status: statusCode, name: err.name, message: message, diff --git a/src/middleware/limit.ts b/src/middleware/limit.ts new file mode 100644 index 00000000..a73e6c82 --- /dev/null +++ b/src/middleware/limit.ts @@ -0,0 +1,59 @@ +import { Request, Response, NextFunction } from 'express'; +import { rateLimit, Options as rateLimiterOptions } from 'express-rate-limit'; +import { slowDown, Options as slowDownOptions } from 'express-slow-down'; +import logger from '@src/scripts/logger'; + + +/* +** configurations +*/ + +const baseOptions: Partial = { + windowMs: 30 * 60 * 1000, + skip: (req, res) => (res.locals.ip == "127.0.0.1" || res.locals.ip == "::1") +} + +const baseSlowDownOptions: Partial = { + ...baseOptions, + delayAfter: 3, // no delay for amount of attempts + delayMs: (used: number) => (used - 3) * 125, // Add delay after delayAfter is reached +} + +const baseRateLimitOptions: Partial = { + ...baseOptions, + limit: 10, // Limit each IP per window + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers +} + + +/* +** cleanup +*/ +const ipsThatReachedLimit: RateLimit.obj = {}; // prevent logs from flooding +setInterval(() => { + const oneHourAgo = Date.now() - 60 * 60 * 1000; + for (const ip in ipsThatReachedLimit) { + if (ipsThatReachedLimit[ip].time < oneHourAgo) { + delete ipsThatReachedLimit[ip]; + } + } +}, 60 * 60 * 1000); + + +/* +** exported section +*/ +export const baseSlowDown = slowDown(baseSlowDownOptions); + +export const errorRateLimiter = rateLimit({ + ...baseRateLimitOptions, + message: 'Too many requests with errors', + handler: (req: Request, res: Response, next: NextFunction, options: rateLimiterOptions) => { + if (!Object.prototype.hasOwnProperty.call(ipsThatReachedLimit, res.locals.ip)) { + logger.error(`[RateLimit] for invalid requests reached ${res.locals.ip}, ${req.get('User-Agent')}`); + ipsThatReachedLimit[res.locals.ip] = { limitReachedOnError: true, time: Date.now() }; + } + res.status(options.statusCode).send(options.message); + } +}); \ No newline at end of file diff --git a/src/models/entry.ts b/src/models/entry.ts index 4305fea3..1f510bf8 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -1,7 +1,7 @@ import { NextFunction, Request, Response } from 'express'; import { checkExact, query } from 'express-validator'; import { crypt } from '@src/scripts/crypt'; -import { create as createError } from '@src/error'; +import { create as createError } from '@src/middleware/error'; import * as file from '@src/scripts/file'; import { getTime } from '@src/scripts/time'; import { getSpeed } from '@src/scripts/speed'; diff --git a/src/scripts/file.ts b/src/scripts/file.ts index 5fdec3fd..a8b693b0 100644 --- a/src/scripts/file.ts +++ b/src/scripts/file.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import path from 'path'; import { promisify } from 'util'; -import { create as createError } from '@src/error'; +import { create as createError } from '@src/middleware/error'; import { NextFunction, Response } from 'express'; import logger from '@src/scripts/logger'; diff --git a/src/scripts/logger.ts b/src/scripts/logger.ts index 0beeaea2..67effdd9 100644 --- a/src/scripts/logger.ts +++ b/src/scripts/logger.ts @@ -1,13 +1,20 @@ // primitive text logger -import fs from 'fs'; // typescript will compile to require -import path from 'path'; // typescript will compile to require -import chalk from "chalk"; // keep import syntax after compile +import fs from 'fs'; +import path from 'path'; +import chalk from "chalk"; -const logPath = path.resolve(__dirname, '../httpdocs', 'log.txt'); +const dirPath = path.resolve(__dirname, '../httpdocs/log'); +const logPath = path.resolve(dirPath, 'start.txt'); + +if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); +} + +// const logPath = path.resolve(__dirname, '../httpdocs/log', 'start.txt'); const date = new Date().toLocaleString('de-DE', { hour12: false }); export default { - log: (message:string|JSON, showDateInConsole:boolean=false, showLogInTest=false) => { + log: (message: string | JSON, showDateInConsole: boolean = false, showLogInTest = false) => { message = JSON.stringify(message); fs.appendFileSync(logPath, `${date} \t|\t ${message} \n`); if (showDateInConsole) { @@ -17,8 +24,29 @@ export default { console.log(message); } }, - error: (message:string|JSON|Response.Error) => { - fs.appendFileSync(logPath, `${date} \t|\t ERROR: ${message} \n`); - console.error(message); + error: (content: string | Response.Error) => { + // logfile + const applyErrorPrefix = !/^\[\w+\]/.test(typeof content == "string" ? content : content.message); + const logMessageTemplate = `${date} \t|\t${applyErrorPrefix ? ' [ERROR]' : ''} ${typeof content == "string" ? content : JSON.stringify(content.message) } \n`; + fs.appendFileSync(logPath, logMessageTemplate); + if (process.env.NODE_ENV == "production") { return; } + + // console + if (typeof content != "string" && Object.hasOwnProperty.call(content, "message")) { + const messageAsString = JSON.stringify(content.message); + if (content.stack) { // replace redundant information + content.stack = content.stack.replace(messageAsString, ""); + } + const consoleMessage = structuredClone(content); // create clone so response output is not "further" affected + consoleMessage.message = messageAsString; // gitbash output improvement (w/o objects in arrays appear as [Object]) + content = consoleMessage; + } else if (typeof content == "string") { + const prefix = content.match(/^\[\w+\]/); + if (prefix?.length) { + content = content.replace(prefix[0], chalk.red(prefix[0])); + } + } + console.error(content); // log string right away or processed Object + } } diff --git a/src/tests/integration.test.ts b/src/tests/integration.test.ts index 2d40ced0..6133c1d1 100644 --- a/src/tests/integration.test.ts +++ b/src/tests/integration.test.ts @@ -129,7 +129,7 @@ describe("GET /write", () => { expect(entry.time.created).toBeGreaterThan(date.getTime()); expect(entry.time.diff).toBeGreaterThan(3.5); - expect(entry.time.diff).toBeLessThan(4); + expect(entry.time.diff).toBeLessThan(4.6); const germanDayPattern = "(Montag|Dienstag|Mittwoch|Donnerstag|Freitag|Samstag|Sonntag)"; diff --git a/types.d.ts b/types.d.ts index 7cb4c689..8931ce5b 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,6 +1,15 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ +namespace RateLimit { + interface obj { + [key: string]: { + limitReachedOnError: boolean, + time: number + } + } +} + namespace Response { interface Message { message: string; From ce6e636c017d412c2535bcf228642f8ca5bb76ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:36:19 +0100 Subject: [PATCH 55/62] Bump follow-redirects from 1.15.5 to 1.15.6 (#47) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.5 to 1.15.6. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.5...v1.15.6) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 085305f0..9b1e43b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3595,9 +3595,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { From 45c3a898a4586086866817409f4143666c6ff9d7 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Thu, 21 Mar 2024 14:14:44 +0100 Subject: [PATCH 56/62] 43 secure output route (#46) * [Task] #43 create color pallette via atmos * [Task] #43 create color pallette via atmos * [Task] #43 cleanup colors and svg * [Task] #41 remove test code * [CHANGE] #3 reconfigured nodemon to copy static files * [Task] #18 replaced getRawBody with builtIn express urlEncoded * [Temp, Task] #43 basic login page, not yet used as middleware * [Temp] #43, create and validate json web token * [Task] #43, add slowDown and RateLimit for failed login attempts * [Task] #43, ratelimit for login page * [Task] #43, add global ratelimiter * [fix] #7, improve error handeling for express errors * [Task] #43 rework body limitations to be checked only appropiate methods * [Task] #43 added check for data before using it * [Task] #43 check that body is ignored for GET in request * [Task] #43 login test * [Task] #43 create tests for login * [Task] #43 fine tune error handling * [Task] #43, finished login and jwt related tests * [Change] #34, no further need for test logging * [Task] #43, fine tune jwt, middleware process improved * [CHANGE] #43 created new esLint to have clientside js without ts * [Temp] #43 test to see new linter configuration * [Change] #43 switched to bcrypt for passwords * [Task] #43 read return json in all cases * [Task] #43 introduced color classes * [Task] #43, prq feedback * [Temp} #43 figuring out why tests dont run on github * [Task] #43 code cleanup --- .eslintrc.json | 2 +- .github/workflows/eslint.yml | 9 +- .github/workflows/main.yml | 28 +- httpdocs/color-table.svg | 159 +++++ httpdocs/css/colors.css | 95 +++ httpdocs/css/login.css | 20 + httpdocs/js/.eslintrc.json | 12 + httpdocs/js/login.js | 1 + nodemon-static.json | 8 + nodemon.json => nodemon-ts.json | 0 package-lock.json | 833 +++++++++++++++++++--- package.json | 23 +- src/app.ts | 46 +- src/controller/read.ts | 139 +++- src/controller/write.ts | 2 +- src/middleware/error.ts | 7 +- src/middleware/limit.ts | 35 +- src/models/entry.ts | 25 +- src/scripts/crypt.ts | 25 +- src/scripts/logger.ts | 7 +- src/tests/app.test.ts | 29 +- src/tests/integration.test.ts | 86 ++- src/tests/login.test.ts | 58 ++ src/tests/{entry.test.ts => unit.test.ts} | 4 +- types.d.ts | 6 +- views/login-form.ejs | 30 + 26 files changed, 1485 insertions(+), 204 deletions(-) create mode 100644 httpdocs/color-table.svg create mode 100644 httpdocs/css/colors.css create mode 100644 httpdocs/css/login.css create mode 100644 httpdocs/js/.eslintrc.json create mode 100644 httpdocs/js/login.js create mode 100644 nodemon-static.json rename nodemon.json => nodemon-ts.json (100%) create mode 100644 src/tests/login.test.ts rename src/tests/{entry.test.ts => unit.test.ts} (96%) create mode 100644 views/login-form.ejs diff --git a/.eslintrc.json b/.eslintrc.json index 30be6b63..aa4c83bb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,6 +22,6 @@ //"@typescript-eslint/no-unused-vars": "warn" "jest/no-conditional-expect": "off" }, - "ignorePatterns": ["dist", "jest.config.js"] + "ignorePatterns": ["dist", "jest.config.js", "httpdocs"] } diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 3aed66af..95578d7b 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -15,8 +15,7 @@ jobs: with: node-version: 16 - run: npm ci # or yarn install - - uses: sibiraj-s/action-eslint@v3 - with: - eslint-args: '--ignore-path=.gitignore --quiet' - extensions: 'js,jsx,ts,tsx' - annotations: true + - name: Lint server-side code + run: npx eslint src/ --fix + - name: Lint client-side code + run: npx eslint httpdocs/js/ --fix diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5eaaa852..e31f2565 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Node3 +name: Tests on: workflow_dispatch: @@ -10,7 +10,13 @@ on: jobs: build: runs-on: ubuntu-latest - + env: + NODE_ENV: ${{ vars.NODE_ENV }} + LOCALHOST: ${{ vars.LOCALHOST }} + LOCALHOSTV6: ${{ vars.LOCALHOSTV6 }} + KEYA: ${{ secrets.KEYA }} + KEYB: ${{ secrets.KEYB }} + USER_TEST: ${{ secrets.USER_TEST }} steps: - uses: actions/checkout@v3 @@ -19,14 +25,24 @@ jobs: with: node-version: '20' cache: 'npm' + - run: echo "NODE_ENV = $NODE_ENV" - run: npm ci - run: npm run build --if-present - name: Start server run: | - sudo npm start & - sleep 8 # Give server some time to start + sudo NODE_ENV=$NODE_ENV LOCALHOST=$LOCALHOST LOCALHOSTV6=$LOCALHOSTV6 KEYA=$KEYA KEYB=$KEYB USER_TEST=$USER_TEST npm start & + sleep 15 # Give server some time to start - name: Check if server is running run: | curl --fail http://localhost:80 || exit 1 - - name: Run tests - run: npm run test \ No newline at end of file + - name: Run app tests + run: npm run test:app + - name: Run login tests + run: npm run test:login + - name: Run unit tests + run: npm run test:unit + - name: Run integration tests + run: npm run test:integration + + + \ No newline at end of file diff --git a/httpdocs/color-table.svg b/httpdocs/color-table.svg new file mode 100644 index 00000000..d2999418 --- /dev/null +++ b/httpdocs/color-table.svg @@ -0,0 +1,159 @@ + + + + + + HEX + + + main + + + info + + + alert + + + success + + + neutral + + + + OKLCH + + + + + 900 + + + + 750 + + + + 625 + + + 500 + + + 375 + + + 250 + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/httpdocs/css/colors.css b/httpdocs/css/colors.css new file mode 100644 index 00000000..d77a7fd5 --- /dev/null +++ b/httpdocs/css/colors.css @@ -0,0 +1,95 @@ +/* +created by atmos https://app.atmos.style/65cc9eaec76d443c0a796d4b + +** base configuration colors ** +Main: #f90 +Info: #231aee +Danger: #ff0000 +Success: #59ec04 +Neutral: #131211 +*/ + +[class*=color] { + --lightness: 67.66%; + --hue: 64.55; + --chroma: 0.007; + color: oklch(var(--lightness) var(--chroma) var(--hue)); + + &[class*=l1] {--lightness: 10%;} + &[class*=l2] {--lightness: 25%;} + &[class*=l3] {--lightness: 37.5%;} + &[class*=l4] {--lightness: 50%;} + &[class*=l5] {--lightness: 62.5%;} + &[class*=l6] {--lightness: 77.2%;} + &[class*=l7] {--lightness: 90%;} + + &[class*=main] { + --lightness: 77.2%; + --chroma: 0.1738; + --hue: 64.55; + + &[class*=l1] {--chroma: 0.02;} + &[class*=l2] {--chroma: 0.056;} + &[class*=l3] {--chroma: 0.085;} + &[class*=l4] {--chroma: 0.114;} + &[class*=l5] {--chroma: 0.142;} + &[class*=l6] {--chroma: 0.1738;} /* base */ + &[class*=l7] {--chroma: 0.06;} + } + + &[class*=info] { + --lightness: 44.87%; + --chroma: 0.2838; + --hue: 268.0; + + &[class*=l1] {--chroma: 0.055;} + &[class*=l2] {--chroma: 0.158;} + &[class*=l3] {--chroma: 0.237;} + &[class*=l4] {--chroma: 0.2838;} /* base */ + &[class*=l5] {--chroma: 0.19;} + &[class*=l6] {--chroma: 0.109;} + &[class*=l7] {--chroma: 0.04;} + } + + &[class*=alert] { + --lightness: 62.8%; + --chroma: 0.2577; + --hue: 29.23; + + &[class*=l1] {--chroma: 0.036;} + &[class*=l2] {--chroma: 0.103;} + &[class*=l3] {--chroma: 0.154;} + &[class*=l4] {--chroma: 0.195;} + &[class*=l5] {--chroma: 0.2577;} /* base */ + &[class*=l6] {--chroma: 0.133;} + &[class*=l7] {--chroma: 0.045;} + } + + &[class*=success] { + --lightness: 83%; + --chroma: 0.2607; + --hue: 138.96; + + &[class*=l1] {--chroma: 0.029;} + &[class*=l2] {--chroma: 0.083;} + &[class*=l3] {--chroma: 0.124;} + &[class*=l4] {--chroma: 0.157;} + &[class*=l5] {--chroma: 0.208;} + &[class*=l6] {--chroma: 0.2607;} /* base */ + &[class*=l7] {--chroma: 0.201;} + } + + &[class*=neutral] { + --lightness: 18.3%; + --chroma: 0.0026; + --hue: 67.66; + + &[class*=l1] {--chroma: 0.001;} + &[class*=l2] {--chroma: 0.0026;} /* base */ + &[class*=l3] {--chroma: 0.006;} + &[class*=l4] {--chroma: 0.007;} + &[class*=l5] {--chroma: 0.009;} + &[class*=l6] {--chroma: 0.011;} + &[class*=l7] {--chroma: 0.004;} + } +} diff --git a/httpdocs/css/login.css b/httpdocs/css/login.css new file mode 100644 index 00000000..b9d1d7c7 --- /dev/null +++ b/httpdocs/css/login.css @@ -0,0 +1,20 @@ +form { + margin-inline: auto; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + max-width: 500px; + gap: 10px; +} +input, button { + flex-grow: 1; +} +textarea, h1 { + flex-basis: 100%; +} +textarea { + height: 50vh; +} +h1 { + text-align: center; +} diff --git a/httpdocs/js/.eslintrc.json b/httpdocs/js/.eslintrc.json new file mode 100644 index 00000000..487f0f60 --- /dev/null +++ b/httpdocs/js/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "root": true, + "env": { + "browser": true, + "es2021": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2021, + "sourceType": "module" + } +} diff --git a/httpdocs/js/login.js b/httpdocs/js/login.js new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/httpdocs/js/login.js @@ -0,0 +1 @@ + diff --git a/nodemon-static.json b/nodemon-static.json new file mode 100644 index 00000000..221cf6c6 --- /dev/null +++ b/nodemon-static.json @@ -0,0 +1,8 @@ +{ + "watch": [ + "httpdocs" + ], + "ext": "*", + "ignore": [], + "exec": "cp -R httpdocs/ dist/" +} \ No newline at end of file diff --git a/nodemon.json b/nodemon-ts.json similarity index 100% rename from nodemon.json rename to nodemon-ts.json diff --git a/package-lock.json b/package-lock.json index 9b1e43b7..bae40f0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,16 +8,18 @@ "name": "lorex", "version": "0.0.1", "dependencies": { + "bcrypt": "^5.1.1", "chalk": "^4.1.2", "compression": "^1.7.4", + "ejs": "^3.1.9", "express": "^4.18.2", "express-rate-limit": "^7.1.5", "express-slow-down": "^2.0.1", "express-validator": "^7.0.1", "helmet": "^7.1.0", "hpp": "^0.2.3", + "jsonwebtoken": "^9.0.2", "module-alias": "^2.2.3", - "raw-body": "^2.5.2", "toobusy-js": "^0.5.1" }, "devDependencies": { @@ -25,15 +27,16 @@ "@tsconfig/node20": "^20.1.2", "@types/bcrypt": "^5.0.2", "@types/compression": "^1.7.5", - "@types/dotenv": "^8.2.0", "@types/express": "^4.17.21", "@types/hpp": "^0.2.5", "@types/jest": "^29.5.11", + "@types/jsonwebtoken": "^9.0.6", "@types/node": "^20.10.6", "@types/toobusy-js": "^0.5.4", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", "axios": "^1.6.5", + "concurrently": "^8.2.2", "dotenv": "^16.3.1", "eslint": "^8.56.0", "eslint-plugin-jest": "^27.6.3", @@ -657,6 +660,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -1371,6 +1386,61 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1538,16 +1608,6 @@ "@types/node": "*" } }, - "node_modules/@types/dotenv": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-ylSC9GhfRH7m1EUXBXofhgx4lUWmFeQDINW5oLuS+gxWdfUeW4zJdeVTYVkexEW+e2VUvlZR2kGnGGipAWR7kw==", - "deprecated": "This is a stub types definition. dotenv provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "dotenv": "*" - } - }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -1636,6 +1696,15 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", + "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -2032,8 +2101,7 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "node_modules/accepts": { "version": "1.3.8", @@ -2077,6 +2145,38 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2124,7 +2224,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2156,6 +2255,23 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -2182,6 +2298,11 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2293,8 +2414,20 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } }, "node_modules/binary-extensions": { "version": "2.2.0", @@ -2346,7 +2479,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2417,6 +2549,11 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2432,13 +2569,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2552,6 +2694,14 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -2619,6 +2769,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2675,8 +2833,63 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "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" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "node_modules/content-disposition": { "version": "0.5.4", @@ -2757,6 +2970,22 @@ "node": ">= 8" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2795,16 +3024,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/delayed-stream": { @@ -2816,6 +3048,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2833,6 +3070,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2896,11 +3141,33 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/ejs": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.640", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.640.tgz", @@ -2922,8 +3189,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { "version": "1.0.2", @@ -2942,6 +3208,25 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -3529,6 +3814,33 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3644,11 +3956,32 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -3672,6 +4005,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3691,15 +4043,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3729,7 +4085,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3825,11 +4180,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3857,10 +4212,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dependencies": { "function-bind": "^1.1.2" }, @@ -3909,6 +4269,39 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3992,7 +4385,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -4054,7 +4446,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -4235,6 +4626,23 @@ "node": ">=8" } }, + "node_modules/jake": { + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -4865,6 +5273,51 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4931,6 +5384,36 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -4943,11 +5426,15 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -5077,7 +5564,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5085,6 +5571,48 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/module-alias": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", @@ -5109,6 +5637,30 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5208,6 +5760,25 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -5239,7 +5810,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -5366,7 +5936,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5630,26 +6199,25 @@ "node": ">= 0.6" } }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5662,6 +6230,12 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5741,7 +6315,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -5775,6 +6348,21 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -5803,7 +6391,6 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -5856,15 +6443,22 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -5896,6 +6490,15 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -5912,8 +6515,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/simple-update-notifier": { "version": "2.0.0", @@ -5961,6 +6563,12 @@ "source-map": "^0.6.0" } }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -5996,6 +6604,14 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -6013,7 +6629,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6027,7 +6642,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -6089,6 +6703,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -6164,6 +6794,20 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -6400,6 +7044,11 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -6463,6 +7112,20 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6478,6 +7141,14 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -6498,8 +7169,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", @@ -6526,8 +7196,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs": { "version": "17.7.2", diff --git a/package.json b/package.json index 8b597505..d42439dc 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,20 @@ "main": "index.js", "scripts": { "prebuild": "rm -rf dist/*", - "build": "npx tsc && cp -R httpdocs/ dist/", - "build:prod": "npx tsc -p ./tsconfig.prod.json && cp -R httpdocs/ dist/", + "build": "npx tsc", + "build:prod": "npx tsc -p ./tsconfig.prod.json", + "postbuild": "cp -R httpdocs/ dist/", "start": "node dist/app.js", - "dev": "rm -rf dist/* && cp -R httpdocs/ dist/ && nodemon src/app.ts", + "dev": "npm run prebuild && npm run postbuild && concurrently \"npm:dev:*\"", + "dev:ts": "nodemon --config nodemon-ts.json", + "dev:static": "nodemon --config nodemon-static.json", "lint": "eslint . --fix", - "test": "jest" + "lint:client": "eslint httpdocs/js/ --fix", + "test": "jest", + "test:app": "jest src/tests/app.test.ts", + "test:login": "jest src/tests/login.test.ts", + "test:unit": "jest src/tests/unit.test.ts", + "test:integration": "jest src/tests/integration.test.ts" }, "keywords": [], "author": "Type-Style", @@ -19,15 +27,16 @@ "@tsconfig/node20": "^20.1.2", "@types/bcrypt": "^5.0.2", "@types/compression": "^1.7.5", - "@types/dotenv": "^8.2.0", "@types/express": "^4.17.21", "@types/hpp": "^0.2.5", "@types/jest": "^29.5.11", + "@types/jsonwebtoken": "^9.0.6", "@types/node": "^20.10.6", "@types/toobusy-js": "^0.5.4", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", "axios": "^1.6.5", + "concurrently": "^8.2.2", "dotenv": "^16.3.1", "eslint": "^8.56.0", "eslint-plugin-jest": "^27.6.3", @@ -38,16 +47,18 @@ "typescript": "^5.3.3" }, "dependencies": { + "bcrypt": "^5.1.1", "chalk": "^4.1.2", "compression": "^1.7.4", + "ejs": "^3.1.9", "express": "^4.18.2", "express-rate-limit": "^7.1.5", "express-slow-down": "^2.0.1", "express-validator": "^7.0.1", "helmet": "^7.1.0", "hpp": "^0.2.3", + "jsonwebtoken": "^9.0.2", "module-alias": "^2.2.3", - "raw-body": "^2.5.2", "toobusy-js": "^0.5.1" }, "_moduleAliases": { diff --git a/src/app.ts b/src/app.ts index 1e59d969..ce1edff4 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,27 +2,24 @@ require('module-alias/register'); import { config } from 'dotenv'; import express from 'express'; import toobusy from 'toobusy-js'; -// import { rateLimit } from 'express-rate-limit'; -// import { slowDown } from 'express-slow-down'; import compression from 'compression'; import helmet from 'helmet'; import hpp from 'hpp'; -import getRawBody from 'raw-body'; import cache from './middleware/cache'; import * as error from "./middleware/error"; import writeRouter from '@src/controller/write'; import readRouter from '@src/controller/read'; import path from 'path'; import logger from '@src/scripts/logger'; - -// console.log({ "status": 403, "name": "Error", "message": { "errors": [{ "type": "field", "msg": "Invalid value", "path": "user", "location": "query" }, { "type": "field", "msg": "is required", "path": "lat", "location": "query" }]}}); -// console.log(JSON.stringify({ "status": 403, "name": "Error", "message": { "errors": [{ "type": "field", "msg": "Invalid value", "path": "user", "location": "query" }, { "type": "field", "msg": "is required", "path": "lat", "location": "query" }]}}, null, 2)); +import { baseRateLimiter } from './middleware/limit'; // configurations config(); // dotenv const app = express(); +app.set('view engine', 'ejs'); + app.use((req, res, next) => { // monitor eventloop to block requests if busy if (toobusy()) { res.status(503).set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Retry-After': '60' }).send("I'm busy right now, sorry."); @@ -33,44 +30,29 @@ app.use((req, res, next) => { // clean up IPv6 Addresses res.locals.ip = req.ip.startsWith('::ffff:') ? req.ip.substring(7) : req.ip; next(); } else { - const message = "No IP provided" + const message = "No IP provided"; logger.error(message); res.status(400).send(message); } - }) -// const slowDownLimiter = slowDown({ -// windowMs: 1 * 60 * 1000, -// delayAfter: 5, // Allow 5 requests per 15 minutes. -// delayMs: (used) => (used - 5) * 1000, // Add delay after delayAfter is reached -// }) - -// const rateLimiter = rateLimit({ -// windowMs: 1 * 60 * 1000, -// limit: 10, // Limit each IP per `window` -// standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers -// legacyHeaders: false, // Disable the `X-RateLimit-*` headers -// }) - app.use(helmet({ contentSecurityPolicy: { directives: { "default-src": "'self'", "img-src": "*" } } })); app.use(cache); app.use(compression()) app.use(hpp()); -app.use(function (req, res, next) { // limit request size limit when recieving data - if (!['POST', 'PUT', 'DELETE'].includes(req.method)) { return next(); } - getRawBody(req, { length: req.headers['content-length'], limit: '1mb', encoding: true }, - function (err) { - if (err) { return next(err) } - next() - } - ) -}) +app.use(baseRateLimiter); +app.use((req, res, next) => { // limit body for specific http methods + if(['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) { + return express.urlencoded({ limit: '0.5kb', extended: true })(req, res, next); + } + next(); +}); + // routes app.get('/', (req, res) => { - console.log(req.ip + " - " + res.locals.ip); - res.send('Hello World, via TypeScript and Node.js! ' + res.locals.ip); + logger.log(req.ip + " - " + res.locals.ip, true); + res.send('Hello World, via TypeScript and Node.js! ' + `ENV: ${process.env.NODE_ENV}`); }); app.use('/write', writeRouter); diff --git a/src/controller/read.ts b/src/controller/read.ts index 02294dc6..b04893da 100644 --- a/src/controller/read.ts +++ b/src/controller/read.ts @@ -2,33 +2,138 @@ import express, { Request, Response, NextFunction } from 'express'; import * as file from '@src/scripts/file'; import { create as createError } from '@src/middleware/error'; import { validationResult, query } from 'express-validator'; +import jwt from 'jsonwebtoken'; +import logger from '@src/scripts/logger'; +import { crypt, compare } from '@src/scripts/crypt'; +import { loginSlowDown, loginLimiter, baseSlowDown, baseRateLimiter } from '@src/middleware/limit'; const router = express.Router(); -router.get('/', - [query('index').isInt().withMessage("not an integer") +router.get('/', + isLoggedIn, + [query('index').isInt().withMessage("not an integer") .isLength({ max: 3 }).withMessage("not in range") .toInt()], - async function getRead(req:Request, res:Response, next:NextFunction) { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return createError(res, 400, JSON.stringify({errors: errors.array()}), next) - } + async function getRead(req: Request, res: Response, next: NextFunction) { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return createError(res, 400, JSON.stringify({ errors: errors.array() }), next) + } + + const fileObj: File.Obj = file.getFile(res, next); + fileObj.content = await file.readAsJson(res, fileObj.path, next) + if (!fileObj.content || !Array.isArray(fileObj.content.entries)) { + return createError(res, undefined, `File corrupt: ${fileObj.path}`, next); + } + + let entries = fileObj.content.entries; + + if (req.query.index) { + entries = entries.slice(Number(req.query.index)); + } + + res.json({ entries }); + }); + +router.get("/login/", baseSlowDown, baseRateLimiter, async function login(req: Request, res: Response) { + res.locals.text = "start"; + loginLimiter(req, res, () => { + res.render("login-form"); + }); +}); + +router.post("/login/", loginSlowDown, async function postLogin(req: Request, res: Response, next: NextFunction) { + logger.log(req.body); + loginLimiter(req, res, async () => { + let validLogin = false; + const user = req.body.user; + const password = req.body.password; + let userFound = false; + if (!user || !password) { + return createError(res, 422, "Body does not contain all expected information", next); + } + + // Loop through all environment variables + for (const key in process.env) { + if (!key.startsWith('USER')) { continue; } + if (key.substring(5) == user) { + userFound = true; + const hash = process.env[key]; + if (hash) { + validLogin = await compare(password, hash); + } + } + } - const fileObj: File.Obj = file.getFile(res, next); - fileObj.content = await file.readAsJson(res, fileObj.path, next) - if (!fileObj.content || !Array.isArray(fileObj.content.entries)) { - return createError(res, undefined, `File corrupt: ${fileObj.path}`, next); + // only allow test user in test environment + if (user == "TEST" && validLogin && process.env.NODE_ENV == "production") { + validLogin = false; + } + + if (validLogin) { + const token = createToken(req, res); + res.json({ "token": token }); + } else { + if (!userFound) { + await crypt(password); // If no matching user is found, perform a dummy password comparison to prevent timing attacks + } + return createError(res, 403, `invalid login credentials`, next); + } + }); +}); + +function isLoggedIn(req: Request, res: Response, next: NextFunction) { + const result = validateToken(req); + if (!result.success) { + createError(res, result.status, result.message || "", next) + } else { + next(); } +} + +function validateToken(req: Request) { + const key = process.env.KEYA; + const header = req.header('Authorization'); + const [type, token] = header ? header.split(' ') : ""; + let payload: string | jwt.JwtPayload = ""; - let entries = fileObj.content.entries; + // Guard; aka early return for common failures before verifying authorization + if (!key) { return { success: false, status: 500, message: 'Wrong Configuration' }; } + if (!header) { return { success: false, status: 401, message: 'No Authorization header' }; } + if (type !== 'Bearer' || !token) { return { success: false, status: 400, message: 'Invalid Authorization header' }; } - if (req.query.index) { - entries = entries.slice(Number(req.query.index)); + try { + payload = jwt.verify(token, key); + } catch (err) { + let message = "could not verify"; + if (err instanceof Error) { + message = `${err.name} - ${err.message}`; + } + + return { success: false, status: 403, message: message }; } - res.json({entries}); -}); + // don't allow test user in production environment + if (typeof payload == "object" && payload.user == "TEST" && process.env.NODE_ENV == "production") { + return { success: false, status: 403, message: 'test user not allowed on production' }; + } + + return { success: true }; +} +function createToken(req: Request, res: Response) { + const key = process.env.KEYA; + if (!key) { throw new Error('Configuration is wrong'); } + const today = new Date(); + const dateString = today.toLocaleDateString("de-DE", { weekday: "short", year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }); + const payload = { + date: dateString, + user: req.body.user + }; + const token = jwt.sign(payload, key, { expiresIn: 60 * 2 }); + res.locals.token = token; + logger.log(JSON.stringify(payload), true); + return token; +} -export default router; \ No newline at end of file +export default router; diff --git a/src/controller/write.ts b/src/controller/write.ts index 7b405ffd..28aa4395 100644 --- a/src/controller/write.ts +++ b/src/controller/write.ts @@ -43,7 +43,7 @@ async function writeData(req: Request, res: Response, next: NextFunction) { const router = express.Router(); - router.get('/', baseSlowDown, entry.validate, errorChecking, writeData); +router.get('/', baseSlowDown, entry.validate, errorChecking, writeData); router.head('/', baseSlowDown, entry.validate, errorChecking); export default router; \ No newline at end of file diff --git a/src/middleware/error.ts b/src/middleware/error.ts index 2eaf6e8d..8db272ee 100644 --- a/src/middleware/error.ts +++ b/src/middleware/error.ts @@ -17,8 +17,11 @@ export function notFound(req: Request, res: Response, next: NextFunction) { next(error); } -export function handler(err: Error, req: Request, res: Response, next: NextFunction) { - const statusCode = res.statusCode !== 200 ? res.statusCode : 500; +export function handler(err: HttpError, req: Request, res: Response, next: NextFunction) { + let statusCode = res.statusCode; + if (statusCode == 200) { + statusCode = err.statusCode || err.status || 500 + } res.status(statusCode); let message; diff --git a/src/middleware/limit.ts b/src/middleware/limit.ts index a73e6c82..eb9aea52 100644 --- a/src/middleware/limit.ts +++ b/src/middleware/limit.ts @@ -3,13 +3,11 @@ import { rateLimit, Options as rateLimiterOptions } from 'express-rate-limit'; import { slowDown, Options as slowDownOptions } from 'express-slow-down'; import logger from '@src/scripts/logger'; - /* ** configurations */ - const baseOptions: Partial = { - windowMs: 30 * 60 * 1000, + windowMs: 3 * 60 * 1000, skip: (req, res) => (res.locals.ip == "127.0.0.1" || res.locals.ip == "::1") } @@ -21,9 +19,17 @@ const baseSlowDownOptions: Partial = { const baseRateLimitOptions: Partial = { ...baseOptions, - limit: 10, // Limit each IP per window + limit: 50, // Limit each IP per window standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers + handler: function rateHandler(req: Request, res: Response, next: NextFunction, options: rateLimiterOptions) { + if (!Object.prototype.hasOwnProperty.call(ipsThatReachedLimit, res.locals.ip)) { + logger.error(`[RateLimit] reached ${req.originalUrl}, ${res.locals.ip}, ${req.get('User-Agent')}`); + ipsThatReachedLimit[res.locals.ip] = { limitReachedOnError: true, time: Date.now() }; + } + res.status(options.statusCode).send(options.message); + }, + message: "Too many requests" } @@ -46,14 +52,21 @@ setInterval(() => { */ export const baseSlowDown = slowDown(baseSlowDownOptions); +export const loginSlowDown = slowDown({ + ...baseSlowDownOptions, + delayAfter: 1, // no delay for amount of attempts + delayMs: (used: number) => (used - 1) * 250, // Add delay after delayAfter is reached + }); + +export const baseRateLimiter = rateLimit(baseRateLimitOptions); + export const errorRateLimiter = rateLimit({ ...baseRateLimitOptions, message: 'Too many requests with errors', - handler: (req: Request, res: Response, next: NextFunction, options: rateLimiterOptions) => { - if (!Object.prototype.hasOwnProperty.call(ipsThatReachedLimit, res.locals.ip)) { - logger.error(`[RateLimit] for invalid requests reached ${res.locals.ip}, ${req.get('User-Agent')}`); - ipsThatReachedLimit[res.locals.ip] = { limitReachedOnError: true, time: Date.now() }; - } - res.status(options.statusCode).send(options.message); - } +}); + +export const loginLimiter = rateLimit({ + ...baseRateLimitOptions, + limit: 3, + message: 'Too many attempts without valid login', }); \ No newline at end of file diff --git a/src/models/entry.ts b/src/models/entry.ts index 1f510bf8..e7fc107e 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -1,6 +1,6 @@ import { NextFunction, Request, Response } from 'express'; import { checkExact, query } from 'express-validator'; -import { crypt } from '@src/scripts/crypt'; +import { compare } from '@src/scripts/crypt'; import { create as createError } from '@src/middleware/error'; import * as file from '@src/scripts/file'; import { getTime } from '@src/scripts/time'; @@ -51,7 +51,7 @@ export const entry = { } } else { entries.push(entry); - } + } file.write(res, fileObj, next); @@ -102,10 +102,6 @@ export function checkTime(value: string) { throw new Error('Timestamp should represent a valid date'); } - if (process.env.NODE_ENV == "development") { - return true; // dev testing convenience - } - const now = new Date(); const difference = now.getTime() - date.getTime(); const oneDayInMilliseconds = 24 * 60 * 60 * 1000; @@ -117,23 +113,16 @@ export function checkTime(value: string) { } -function checkKey(value: string) { +async function checkKey(value: string) { + if (!value) { throw new Error('Key required'); } + if (!process.env.KEYB) { throw new Error('Configuration wrong'); } if (process.env.NODE_ENV != "production" && value == "test") { return true; // dev testing convenience } - if (!value) { - throw new Error('Key required'); - } - - value = decodeURIComponent(value); + const result = await compare(decodeURIComponent(value), process.env.KEYB); - const hash = crypt(value); - - if (process.env.KEYB != hash) { - if (process.env.NODE_ENV == "development") { - console.log(hash); - } + if (!result) { throw new Error('Key does not match'); } diff --git a/src/scripts/crypt.ts b/src/scripts/crypt.ts index b14ef427..1928c521 100644 --- a/src/scripts/crypt.ts +++ b/src/scripts/crypt.ts @@ -1,9 +1,20 @@ -import * as crypto from 'crypto'; +import * as bcrypt from "bcrypt"; +import crypto from "crypto"; -export const crypt = function (value:string) { +export const crypt = async function (password: string, quick = false) { + const extendedPassword = pepper(password); + return await bcrypt.hash(extendedPassword, quick ? 8 : 16); +}; + +export const compare = async function (password: string, hash: string) { + const extendedPassword = pepper(password); + return await bcrypt.compare(extendedPassword, hash) +} + +function pepper(password: string) { const key = process.env.KEYA; - if (!key) { - throw new Error('KEYA is not defined in the environment variables'); - } - return crypto.createHmac('sha256', key).update(value).digest("base64"); -}; \ No newline at end of file + if (!key) { throw new Error('KEYA is not defined in the environment variables'); } + return password + crypto.createHmac('sha256', key).digest("base64"); +} + + diff --git a/src/scripts/logger.ts b/src/scripts/logger.ts index 67effdd9..ddd608a1 100644 --- a/src/scripts/logger.ts +++ b/src/scripts/logger.ts @@ -10,17 +10,16 @@ if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } -// const logPath = path.resolve(__dirname, '../httpdocs/log', 'start.txt'); const date = new Date().toLocaleString('de-DE', { hour12: false }); export default { - log: (message: string | JSON, showDateInConsole: boolean = false, showLogInTest = false) => { + log: (message: string | JSON, showDateInConsole: boolean = false) => { message = JSON.stringify(message); fs.appendFileSync(logPath, `${date} \t|\t ${message} \n`); if (showDateInConsole) { message = `${chalk.dim(date + ":")} ${message}`; } - if (process.env.NODE_ENV == "development" || showLogInTest && process.env.NODE_ENV == "test") { + if (process.env.NODE_ENV != "production") { console.log(message); } }, @@ -46,7 +45,7 @@ export default { content = content.replace(prefix[0], chalk.red(prefix[0])); } } - console.error(content); // log string right away or processed Object + console.error(content); } } diff --git a/src/tests/app.test.ts b/src/tests/app.test.ts index b7c01c25..44ba08dc 100644 --- a/src/tests/app.test.ts +++ b/src/tests/app.test.ts @@ -1,4 +1,10 @@ import axios from 'axios'; +import qs from 'qs'; + +// random data of 0.75Kb pre GZIP +const randomData = qs.stringify({ + randomData: 'zIakHvSaXDdLtaPaL02LhGr4Fk6hzXF7tELeR733YZyyye1fnjNzrSlHgqcHU8BKqvE5Mi4B7iHIEdqjTelpoWyaqXqX8l6LzOvROAkTF4lrLXLD1oMHwDL9hnjR0P7g0BB2DqagKkoEYD4TmXeAXT9PbevbirWnOEzmIgSv65SlsNTRFYhmzWl93twXEBNclHTCTnZpf6diWoo8FsXZR49pe9v8J1paalh2LlbNF4ZUxMxNpSvSTRHxvkYo0TMpd0NqUSSLduLIWcE1jhCWnmHhsbohDZjFfMhVS8IFvCiu7rxfuWgwMPqD9FcBR79eqJBy2tjDMqA9S1k9k50AkbOQ6USVfEuqOtocqXonTvC3Jml90KYSs0gX4SSTFHofpMtbWIdkuKqZbitQjsPSBpTx27dhFZd8zT4erdE1ltHnq83pjEj9hQYqatmdzQGYnOyh9YDt8i1IJpk4DX83DLzw3QhaFPgZFq98SOj4ILytmBMIqOtD464aF8PKGq6g7dVqYOtyF2FwyY0xgA7LjGaFzaCDjnGEcPIMRc2tcorsuRPKUI0zcde1gYPsn4WKaKUp87hJd1YtorzCXPfvivfGGL5v1XaSzApc9BbZpbxcpTOi4Pgvx7hNafUcaCr6kcjp4JVYSktnnGCwEplgGEF8uCELsEBUi9LNhgsnwgoRh55TaJfcaFfGfYLokXYEgiyOwYhhdEY3kfjHZWAyFS4owCR6nMJGOGMHrQi1fBefdp28PQGwgELix5Vf8j6P' +}); describe('Server Status', () => { it('The server is running', async () => { @@ -12,4 +18,25 @@ describe('Server Status', () => { expect(serverStatus).toBe(200); }) -}) \ No newline at end of file + + it('server is ignoring body on GET requests', async () => { + let serverStatus; + try { + const response = await axios.request({ + url: 'http://localhost:80/', + method: 'GET', + data: randomData, + }); + serverStatus = response.status; + } catch (error) { + console.error(error); + } + + expect(serverStatus).toBe(200); + }) + +}) + + + + diff --git a/src/tests/integration.test.ts b/src/tests/integration.test.ts index 6133c1d1..2e189e0a 100644 --- a/src/tests/integration.test.ts +++ b/src/tests/integration.test.ts @@ -1,4 +1,5 @@ import axios, { AxiosError } from 'axios'; +import qs from 'qs'; import fs from "fs"; import path from "path"; @@ -9,6 +10,7 @@ async function callServer(timestamp = new Date().getTime(), query: string, expec params.set("timestamp", timestamp.toString()); url.search = params.toString(); + let response; if (expectStatus == 200) { if (method == "GET") { @@ -41,44 +43,67 @@ function isInRange(actual: string | number, expected: number, range: number) { return Math.abs(Number(actual) - expected) <= range; } +async function verifiedRequest(url: string, token: string) { + const response = await axios({ + method: 'get', + url: url, + headers: { + 'Authorization': `Bearer ${token}`, + } + }); + return response; +} + + + describe('HEAD /write', () => { + // eslint-disable-next-line jest/expect-expect it('with all parameters correctly set it should succeed', async () => { await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200); }); + // eslint-disable-next-line jest/expect-expect it('without key it sends 403', async () => { await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0", 403); }); + // eslint-disable-next-line jest/expect-expect it('with user length not equal to 2 it sends 422', async () => { await callServer(undefined, "user=x&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); }); + // eslint-disable-next-line jest/expect-expect it('with lat not between -90 and 90 it sends 422', async () => { await callServer(undefined, "user=xx&lat=91.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); }); + // eslint-disable-next-line jest/expect-expect it('with lon not between -180 and 180 it sends 422', async () => { await callServer(undefined, "user=xx&lat=45.000&lon=181.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); }); + // eslint-disable-next-line jest/expect-expect it('with timestamp to old sends 422', async () => { const timestamp = new Date().getTime() - 24 * 60 * 60 * 1000 * 2; // two days ago await callServer(timestamp, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); }) + // eslint-disable-next-line jest/expect-expect it('with hdop not between 0 and 100 it sends 422', async () => { await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); }); + // eslint-disable-next-line jest/expect-expect it('with altitude not between 0 and 10000 it sends 422', async () => { await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test", 422); }); + // eslint-disable-next-line jest/expect-expect it('with speed not between 0 and 300 it sends 422', async () => { await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test", 422); }); + // eslint-disable-next-line jest/expect-expect it('with heading not between 0 and 360 it sends 422', async () => { await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test", 422); }); @@ -92,7 +117,7 @@ describe("GET /write", () => { const filePath = path.resolve(dirPath, `data-${formattedDate}.json`); it('there should a file of the current date', async () => { - await await callServer(undefined, "user=xx&lat=52.51451&lon=13.35105×tamp=R3Pl4C3&hdop=20.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + await callServer(undefined, "user=xx&lat=52.51451&lon=13.35105×tamp=R3Pl4C3&hdop=20.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); fs.access(filePath, fs.constants.F_OK, (err) => { expect(err).toBeFalsy(); @@ -176,13 +201,14 @@ describe("GET /write", () => { expect(entry.ignore).toBe(false); // current one to be false allways expect(lastEntry.ignore).toBe(true); // last one to high hdop to be true - await await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); jsonData = getData(filePath); entry = jsonData.entries[1]; // same data point, but not last now therefore ignore true expect(entry.ignore).toBe(true); }); }); + describe('API calls', () => { test(`1000 api calls`, async () => { for (let i = 0; i < 1000; i++) { @@ -203,15 +229,57 @@ describe('API calls', () => { }); -describe('/read', () => { - test(`returns json`, async () => { - const response = await axios.get("http://localhost:80/read?index=0"); +describe('read and login', () => { + let token = ""; + const testData = qs.stringify({ + user: "TEST", + password: "test", + }); + test(`redirect without logged in`, async () => { + try { + await axios.get("http://localhost:80/read/"); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(401); + } else { + console.error(axiosError); + } + } + }); + + it('test user can login', async () => { + const response = await axios.post('http://localhost:80/read/login', testData); + + expect(response.status).toBe(200); + expect(response.headers['content-type']).toEqual(expect.stringContaining('application/json')); + expect(response).toHaveProperty('data.token'); + expect(response.data.token).not.toBeNull(); + token = response.data.token; + }) + + test('wrong token get error', async () => { + try { + await verifiedRequest("http://localhost:80/read?index=0", "justWrongValue"); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(403); + } else { + console.error(axiosError); + } + } + }); + + test('verified request returns json', async () => { + const response = await verifiedRequest("http://localhost:80/read?index=0", token); expect(response.status).toBe(200); expect(response.headers['content-type']).toEqual(expect.stringContaining('application/json')); }); + test(`index parameter to long`, async () => { try { - await axios.get("http://localhost:80/read?index=1234"); + await verifiedRequest("http://localhost:80/read?index=1234", token); } catch (error) { const axiosError = error as AxiosError; if (axiosError.response) { @@ -221,9 +289,10 @@ describe('/read', () => { } } }); + test(`index parameter to be a number`, async () => { try { - await axios.get("http://localhost:80/read?index=a9"); + await verifiedRequest("http://localhost:80/read?index=a9", token); } catch (error) { const axiosError = error as AxiosError; if (axiosError.response) { @@ -234,7 +303,8 @@ describe('/read', () => { } }); test(`index parameter reduces length of json`, async () => { - const response = await axios.get("http://localhost:80/read?index=999"); + const response = await verifiedRequest("http://localhost:80/read?index=999", token); + expect(response.status).toBe(200); expect(response.data.entries.length).toBe(1); }); }); \ No newline at end of file diff --git a/src/tests/login.test.ts b/src/tests/login.test.ts new file mode 100644 index 00000000..3fefe2da --- /dev/null +++ b/src/tests/login.test.ts @@ -0,0 +1,58 @@ +import axios, { AxiosError } from 'axios'; +import qs from 'qs'; + +const userDataLarge = qs.stringify({ + user: "user", + password: "pass", + kilobyte: 'BPSwVu5vcvhWB17HcfIdyQK83mHJZKChv7zDihBJoifWK9EJFzK7VYf3kUgIqkc0io8DnSdewzc9U0GpzodQUFz0KLMaogsJruEbNSKvxnzUxS5UqSR64lLOmGumoPcn2InC0Ebpqfdiw90HFVZVlE3AY6Lhgbx8ILHi55RvpuGefDjBsePgow8Jh9sc8uVMCDglLmHQ0zk3PumMj0KlOszbMmX9fG0pPUsvLLc40biPBv9t97K3BFjYd3fGriRAQ3bFhGHBz2wzGbNQfHjKFDHuSvXOw8KReM7Wwd4Cl02QQ3RnDJVwH6cayh4BqFRXlP3i6uXw0l9qxdTv0q1CtV9rJho6zwo04gkGLvsS3AoYJQtHnOtUDdHPExu7l3nMKnPoRUwl7K2ePfHRuppFGqa43Q49bI04VjEhrB9k5S2uZJoxZdm63rIUrydmkZWdvBLVVZUIXwwIRnwLmoa26htKOz9FPKwWIPOM0NZj4jAoPhKqLDJwziNZn5UupzxBXoUM3BIyEk3K8GXs7eBduH9GCK2z2HPF0fJNtGiHASe7jCOC2mhSC5zGf9k0Yu1Ey63oQQZUtT7L57lp7UzPE2p6wzKDlbJZOn0Ho5OUfq3hE2C8fQRO1M6jDvRTiUIKhhxSHYd75Pvh4SG9lD8w5OHASusLDxmzKBUuG4GrGrQYpd0awJkqnKp5lk7psLD22YTtjTuDgI500tQLXSslxI1kIuB8RnN1LsxHyRQMVtXmNFOKKZV2U2frWpImIz2wSHCYrwRGygwDtiFfwtVwTapjhQqUMyb1vrWWi3EL1Y50fDCjDDHlvLI4N2tr2DULFf3a9m2SYWSoE6CYP4og5YyqjhqFQFm9urREInyZi9L0iQoMYxEqxTjGiVJfKmaSChSd0kQz6z2OdsxFbkMWJ2CAHOL1XNK8iFFSp93fIspaNMIonRVDCj4ZIP1LaPHDmIYcYTNU4k3Uz6VBHSIc1VjiG3sc2MZpKw9An0tJVlWbtVSk2RGYWIANAYyr5pQS' +}); +const userData = qs.stringify({ + user: "user", + password: "pass" +}); + +describe('Login', () => { + it('form available', async () => { + let serverStatus = {}; + let response = { data: "", status: "" }; + try { + response = await axios.get('http://localhost:80/read/login'); + serverStatus = response.status; + } catch (error) { + console.error(error); + } + + expect(serverStatus).toBe(200); + expect(response.data).toContain(' { + try { + await axios.post('http://localhost:80/read/login', userDataLarge); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(413); + } else { + console.error(axiosError); + } + } + }) + + it('invalid login verification test', async () => { + try { + await axios.post('http://localhost:80/read/login', userData); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(403); + } else { + console.error(axiosError); + } + } + }) +}) + + + + diff --git a/src/tests/entry.test.ts b/src/tests/unit.test.ts similarity index 96% rename from src/tests/entry.test.ts rename to src/tests/unit.test.ts index 48058e7f..013a1b42 100644 --- a/src/tests/entry.test.ts +++ b/src/tests/unit.test.ts @@ -1,7 +1,7 @@ import { checkNumber, checkTime } from "../models/entry"; -describe("checkNumber", () => { +describe("entry checkNumber", () => { it("should throw error if value is not provided", () => { expect(() => checkNumber(0, 100)("")).toThrow(new Error('is required')); }); @@ -19,7 +19,7 @@ describe("checkNumber", () => { }); }); -describe("checkTime", () => { +describe("entry checkTime", () => { it("should throw error if value is not a number", () => { expect(() => checkTime("abc")).toThrow(new Error('Timestamp should be a number')); }); diff --git a/types.d.ts b/types.d.ts index 8931ce5b..6bb5b4cc 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ - namespace RateLimit { interface obj { [key: string]: { @@ -118,3 +117,8 @@ namespace Models { total: number } } + +interface HttpError extends Error { + status?: number; + statusCode?: number; +} \ No newline at end of file diff --git a/views/login-form.ejs b/views/login-form.ejs new file mode 100644 index 00000000..c628aab4 --- /dev/null +++ b/views/login-form.ejs @@ -0,0 +1,30 @@ + + + + + + Login Form - Lorex + + + + + + + + + + \ No newline at end of file From da13c770db9f9d229d3bf799b6e091e8c3e49dbd Mon Sep 17 00:00:00 2001 From: Type-Style Date: Thu, 21 Mar 2024 14:44:02 +0100 Subject: [PATCH 57/62] 48 move login to seperate controller (#49) * [Task] #43, add label to form * [Task] #48 login controller --- src/app.ts | 2 + src/controller/login.ts | 58 +++++++++++++++++++ src/controller/read.ts | 104 +--------------------------------- src/middleware/logged-in.ts | 13 +++++ src/scripts/token.ts | 49 ++++++++++++++++ src/tests/integration.test.ts | 2 +- src/tests/login.test.ts | 6 +- views/login-form.ejs | 5 +- 8 files changed, 131 insertions(+), 108 deletions(-) create mode 100644 src/controller/login.ts create mode 100644 src/middleware/logged-in.ts create mode 100644 src/scripts/token.ts diff --git a/src/app.ts b/src/app.ts index ce1edff4..d7e10840 100644 --- a/src/app.ts +++ b/src/app.ts @@ -9,6 +9,7 @@ import cache from './middleware/cache'; import * as error from "./middleware/error"; import writeRouter from '@src/controller/write'; import readRouter from '@src/controller/read'; +import loginRouter from '@src/controller/login'; import path from 'path'; import logger from '@src/scripts/logger'; import { baseRateLimiter } from './middleware/limit'; @@ -57,6 +58,7 @@ app.get('/', (req, res) => { app.use('/write', writeRouter); app.use('/read', readRouter); +app.use('/login', loginRouter); // use httpdocs as static folder app.use('/', express.static(path.join(__dirname, 'httpdocs'), { diff --git a/src/controller/login.ts b/src/controller/login.ts new file mode 100644 index 00000000..7c712364 --- /dev/null +++ b/src/controller/login.ts @@ -0,0 +1,58 @@ +import express, { Request, Response, NextFunction } from 'express'; +import { create as createError } from '@src/middleware/error'; +import logger from '@src/scripts/logger'; +import { crypt, compare } from '@src/scripts/crypt'; +import { loginSlowDown, loginLimiter, baseSlowDown, baseRateLimiter } from '@src/middleware/limit'; +import { createToken } from '@src/scripts/token'; + +const router = express.Router(); + +router.get("/", baseSlowDown, baseRateLimiter, async function login(req: Request, res: Response) { + res.locals.text = "start"; + loginLimiter(req, res, () => { + res.render("login-form"); + }); +}); + +router.post("/", loginSlowDown, async function postLogin(req: Request, res: Response, next: NextFunction) { + logger.log(req.body); + loginLimiter(req, res, async () => { + let validLogin = false; + const user = req.body.user; + const password = req.body.password; + let userFound = false; + if (!user || !password) { + return createError(res, 422, "Body does not contain all expected information", next); + } + + // Loop through all environment variables + for (const key in process.env) { + if (!key.startsWith('USER')) { continue; } + if (key.substring(5) == user) { + userFound = true; + const hash = process.env[key]; + if (hash) { + validLogin = await compare(password, hash); + } + } + } + + // only allow test user in test environment + if (user == "TEST" && validLogin && process.env.NODE_ENV == "production") { + validLogin = false; + } + + if (validLogin) { + const token = createToken(req, res); + res.json({ "token": token }); + } else { + if (!userFound) { + await crypt(password); // If no matching user is found, perform a dummy password comparison to prevent timing attacks + } + return createError(res, 403, `invalid login credentials`, next); + } + }); +}); + + +export default router; \ No newline at end of file diff --git a/src/controller/read.ts b/src/controller/read.ts index b04893da..bd4b88c8 100644 --- a/src/controller/read.ts +++ b/src/controller/read.ts @@ -2,10 +2,7 @@ import express, { Request, Response, NextFunction } from 'express'; import * as file from '@src/scripts/file'; import { create as createError } from '@src/middleware/error'; import { validationResult, query } from 'express-validator'; -import jwt from 'jsonwebtoken'; -import logger from '@src/scripts/logger'; -import { crypt, compare } from '@src/scripts/crypt'; -import { loginSlowDown, loginLimiter, baseSlowDown, baseRateLimiter } from '@src/middleware/limit'; +import { isLoggedIn } from '@src/middleware/logged-in'; const router = express.Router(); @@ -35,105 +32,6 @@ router.get('/', res.json({ entries }); }); -router.get("/login/", baseSlowDown, baseRateLimiter, async function login(req: Request, res: Response) { - res.locals.text = "start"; - loginLimiter(req, res, () => { - res.render("login-form"); - }); -}); - -router.post("/login/", loginSlowDown, async function postLogin(req: Request, res: Response, next: NextFunction) { - logger.log(req.body); - loginLimiter(req, res, async () => { - let validLogin = false; - const user = req.body.user; - const password = req.body.password; - let userFound = false; - if (!user || !password) { - return createError(res, 422, "Body does not contain all expected information", next); - } - - // Loop through all environment variables - for (const key in process.env) { - if (!key.startsWith('USER')) { continue; } - if (key.substring(5) == user) { - userFound = true; - const hash = process.env[key]; - if (hash) { - validLogin = await compare(password, hash); - } - } - } - - // only allow test user in test environment - if (user == "TEST" && validLogin && process.env.NODE_ENV == "production") { - validLogin = false; - } - - if (validLogin) { - const token = createToken(req, res); - res.json({ "token": token }); - } else { - if (!userFound) { - await crypt(password); // If no matching user is found, perform a dummy password comparison to prevent timing attacks - } - return createError(res, 403, `invalid login credentials`, next); - } - }); -}); - -function isLoggedIn(req: Request, res: Response, next: NextFunction) { - const result = validateToken(req); - if (!result.success) { - createError(res, result.status, result.message || "", next) - } else { - next(); - } -} - -function validateToken(req: Request) { - const key = process.env.KEYA; - const header = req.header('Authorization'); - const [type, token] = header ? header.split(' ') : ""; - let payload: string | jwt.JwtPayload = ""; - - // Guard; aka early return for common failures before verifying authorization - if (!key) { return { success: false, status: 500, message: 'Wrong Configuration' }; } - if (!header) { return { success: false, status: 401, message: 'No Authorization header' }; } - if (type !== 'Bearer' || !token) { return { success: false, status: 400, message: 'Invalid Authorization header' }; } - - try { - payload = jwt.verify(token, key); - } catch (err) { - let message = "could not verify"; - if (err instanceof Error) { - message = `${err.name} - ${err.message}`; - } - - return { success: false, status: 403, message: message }; - } - - // don't allow test user in production environment - if (typeof payload == "object" && payload.user == "TEST" && process.env.NODE_ENV == "production") { - return { success: false, status: 403, message: 'test user not allowed on production' }; - } - - return { success: true }; -} -function createToken(req: Request, res: Response) { - const key = process.env.KEYA; - if (!key) { throw new Error('Configuration is wrong'); } - const today = new Date(); - const dateString = today.toLocaleDateString("de-DE", { weekday: "short", year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }); - const payload = { - date: dateString, - user: req.body.user - }; - const token = jwt.sign(payload, key, { expiresIn: 60 * 2 }); - res.locals.token = token; - logger.log(JSON.stringify(payload), true); - return token; -} export default router; diff --git a/src/middleware/logged-in.ts b/src/middleware/logged-in.ts new file mode 100644 index 00000000..eddca045 --- /dev/null +++ b/src/middleware/logged-in.ts @@ -0,0 +1,13 @@ +import { Request, Response, NextFunction } from 'express'; +import { validateToken } from '@src/scripts/token'; +import { create as createError } from '@src/middleware/error'; + + +export function isLoggedIn(req: Request, res: Response, next: NextFunction) { + const result = validateToken(req); + if (!result.success) { + createError(res, result.status, result.message || "", next) + } else { + next(); + } +} diff --git a/src/scripts/token.ts b/src/scripts/token.ts new file mode 100644 index 00000000..f26a7181 --- /dev/null +++ b/src/scripts/token.ts @@ -0,0 +1,49 @@ +import jwt from 'jsonwebtoken'; +import logger from '@src/scripts/logger'; +import {Request, Response } from 'express'; + + +export function validateToken(req: Request) { + const key = process.env.KEYA; + const header = req.header('Authorization'); + const [type, token] = header ? header.split(' ') : ""; + let payload: string | jwt.JwtPayload = ""; + + // Guard; aka early return for common failures before verifying authorization + if (!key) { return { success: false, status: 500, message: 'Wrong Configuration' }; } + if (!header) { return { success: false, status: 401, message: 'No Authorization header' }; } + if (type !== 'Bearer' || !token) { return { success: false, status: 400, message: 'Invalid Authorization header' }; } + + try { + payload = jwt.verify(token, key); + } catch (err) { + let message = "could not verify"; + if (err instanceof Error) { + message = `${err.name} - ${err.message}`; + } + + return { success: false, status: 403, message: message }; + } + + // don't allow test user in production environment + if (typeof payload == "object" && payload.user == "TEST" && process.env.NODE_ENV == "production") { + return { success: false, status: 403, message: 'test user not allowed on production' }; + } + + return { success: true }; +} + +export function createToken(req: Request, res: Response) { + const key = process.env.KEYA; + if (!key) { throw new Error('Configuration is wrong'); } + const today = new Date(); + const dateString = today.toLocaleDateString("de-DE", { weekday: "short", year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }); + const payload = { + date: dateString, + user: req.body.user + }; + const token = jwt.sign(payload, key, { expiresIn: 60 * 2 }); + res.locals.token = token; + logger.log(JSON.stringify(payload), true); + return token; +} diff --git a/src/tests/integration.test.ts b/src/tests/integration.test.ts index 2e189e0a..4fd5f4e0 100644 --- a/src/tests/integration.test.ts +++ b/src/tests/integration.test.ts @@ -249,7 +249,7 @@ describe('read and login', () => { }); it('test user can login', async () => { - const response = await axios.post('http://localhost:80/read/login', testData); + const response = await axios.post('http://localhost:80/login', testData); expect(response.status).toBe(200); expect(response.headers['content-type']).toEqual(expect.stringContaining('application/json')); diff --git a/src/tests/login.test.ts b/src/tests/login.test.ts index 3fefe2da..6fbc3404 100644 --- a/src/tests/login.test.ts +++ b/src/tests/login.test.ts @@ -16,7 +16,7 @@ describe('Login', () => { let serverStatus = {}; let response = { data: "", status: "" }; try { - response = await axios.get('http://localhost:80/read/login'); + response = await axios.get('http://localhost:80/login'); serverStatus = response.status; } catch (error) { console.error(error); @@ -28,7 +28,7 @@ describe('Login', () => { it('server is blocking requests with large body', async () => { try { - await axios.post('http://localhost:80/read/login', userDataLarge); + await axios.post('http://localhost:80/login', userDataLarge); } catch (error) { const axiosError = error as AxiosError; if (axiosError.response) { @@ -41,7 +41,7 @@ describe('Login', () => { it('invalid login verification test', async () => { try { - await axios.post('http://localhost:80/read/login', userData); + await axios.post('http://localhost:80/login', userData); } catch (error) { const axiosError = error as AxiosError; if (axiosError.response) { diff --git a/views/login-form.ejs b/views/login-form.ejs index c628aab4..802a7c95 100644 --- a/views/login-form.ejs +++ b/views/login-form.ejs @@ -20,7 +20,10 @@ Password: - +

Token: <%= locals.token %>

From 8ab8cba3b7762b9f1c53cad77df37a1654a9343b Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 26 Mar 2024 12:32:13 +0100 Subject: [PATCH 58/62] 50 integrate csrf protection for login form (#53) * [Task] #50, create CSRF Validation for login form * [Task] #43, added icon to repository for later use * [Task] #50, cleanup cetntralized; rename token functions * [Task] #50, reduced token length and improved error handling * [Task] #50 csrf tests added to login * [Task] #50, added test case for csrf, repaired integration --- httpdocs/icon.png | Bin 0 -> 50481 bytes src/app.ts | 13 ++++++--- src/controller/login.ts | 20 +++++++------- src/middleware/limit.ts | 37 +++++++++++++------------- src/middleware/logged-in.ts | 4 +-- src/scripts/crypt.ts | 2 -- src/scripts/token.ts | 48 ++++++++++++++++++++++++++++++---- src/tests/integration.test.ts | 21 ++++++++++++--- src/tests/login.test.ts | 42 ++++++++++++++++++++++++++--- types.d.ts | 5 ++++ views/login-form.ejs | 6 ++--- 11 files changed, 147 insertions(+), 51 deletions(-) create mode 100644 httpdocs/icon.png diff --git a/httpdocs/icon.png b/httpdocs/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..827ec59bfce7546e32bc5aaa563e86ee6a924cc9 GIT binary patch literal 50481 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 VlY&WEpLqlPWF!?nSAH@K`aiw_K*0b2 literal 0 HcmV?d00001 diff --git a/src/app.ts b/src/app.ts index d7e10840..b11cfdb6 100644 --- a/src/app.ts +++ b/src/app.ts @@ -12,7 +12,8 @@ import readRouter from '@src/controller/read'; import loginRouter from '@src/controller/login'; import path from 'path'; import logger from '@src/scripts/logger'; -import { baseRateLimiter } from './middleware/limit'; +import { baseRateLimiter, cleanup as cleanupRateLimitedIps } from './middleware/limit'; +import { cleanupCSRF } from "@src/scripts/token"; // configurations config(); // dotenv @@ -43,8 +44,8 @@ app.use(compression()) app.use(hpp()); app.use(baseRateLimiter); app.use((req, res, next) => { // limit body for specific http methods - if(['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) { - return express.urlencoded({ limit: '0.5kb', extended: true })(req, res, next); + if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) { + return express.urlencoded({ limit: '0.5kb', extended: true })(req, res, next); } next(); }); @@ -75,6 +76,12 @@ const server = app.listen(80, () => { logger.log(`Server running //localhost:80, ENV: ${process.env.NODE_ENV}`, true); }); +// scheduled cleanup +setInterval(() => { + cleanupCSRF(); + cleanupRateLimitedIps(); +}, 1000 * 60 * 5); + // catching shutdowns ['SIGINT', 'SIGTERM', 'exit'].forEach((signal) => { process.on(signal, () => { diff --git a/src/controller/login.ts b/src/controller/login.ts index 7c712364..6a47f4cc 100644 --- a/src/controller/login.ts +++ b/src/controller/login.ts @@ -1,29 +1,29 @@ import express, { Request, Response, NextFunction } from 'express'; import { create as createError } from '@src/middleware/error'; -import logger from '@src/scripts/logger'; import { crypt, compare } from '@src/scripts/crypt'; import { loginSlowDown, loginLimiter, baseSlowDown, baseRateLimiter } from '@src/middleware/limit'; -import { createToken } from '@src/scripts/token'; +import { createJWT, createCSRF, validateCSRF } from '@src/scripts/token'; + const router = express.Router(); -router.get("/", baseSlowDown, baseRateLimiter, async function login(req: Request, res: Response) { - res.locals.text = "start"; +router.get("/", baseSlowDown, baseRateLimiter, async function login(req: Request, res: Response, next: NextFunction) { loginLimiter(req, res, () => { + const csrfToken = createCSRF(res, next); + res.locals = {...res.locals, text: 'start', csrfToken: csrfToken}; res.render("login-form"); }); }); router.post("/", loginSlowDown, async function postLogin(req: Request, res: Response, next: NextFunction) { - logger.log(req.body); loginLimiter(req, res, async () => { let validLogin = false; + const token = req.body.csrfToken; const user = req.body.user; const password = req.body.password; let userFound = false; - if (!user || !password) { - return createError(res, 422, "Body does not contain all expected information", next); - } + if (!user || !password) { return createError(res, 422, "Body does not contain all expected information", next); } + if (!token || !validateCSRF(req.body.csrfToken)) { return createError(res, 403, "Invalid CSRF Token", next); } // Loop through all environment variables for (const key in process.env) { @@ -43,13 +43,13 @@ router.post("/", loginSlowDown, async function postLogin(req: Request, res: Resp } if (validLogin) { - const token = createToken(req, res); + const token = createJWT(req, res); res.json({ "token": token }); } else { if (!userFound) { await crypt(password); // If no matching user is found, perform a dummy password comparison to prevent timing attacks } - return createError(res, 403, `invalid login credentials`, next); + return createError(res, 403, `Invalid credentials`, next); } }); }); diff --git a/src/middleware/limit.ts b/src/middleware/limit.ts index eb9aea52..13010713 100644 --- a/src/middleware/limit.ts +++ b/src/middleware/limit.ts @@ -3,6 +3,8 @@ import { rateLimit, Options as rateLimiterOptions } from 'express-rate-limit'; import { slowDown, Options as slowDownOptions } from 'express-slow-down'; import logger from '@src/scripts/logger'; +const ipsThatReachedLimit: RateLimit.obj = {}; // prevent logs from flooding + /* ** configurations */ @@ -33,30 +35,17 @@ const baseRateLimitOptions: Partial = { } -/* -** cleanup -*/ -const ipsThatReachedLimit: RateLimit.obj = {}; // prevent logs from flooding -setInterval(() => { - const oneHourAgo = Date.now() - 60 * 60 * 1000; - for (const ip in ipsThatReachedLimit) { - if (ipsThatReachedLimit[ip].time < oneHourAgo) { - delete ipsThatReachedLimit[ip]; - } - } -}, 60 * 60 * 1000); - /* ** exported section */ export const baseSlowDown = slowDown(baseSlowDownOptions); -export const loginSlowDown = slowDown({ - ...baseSlowDownOptions, - delayAfter: 1, // no delay for amount of attempts - delayMs: (used: number) => (used - 1) * 250, // Add delay after delayAfter is reached - }); +export const loginSlowDown = slowDown({ + ...baseSlowDownOptions, + delayAfter: 1, // no delay for amount of attempts + delayMs: (used: number) => (used - 1) * 250, // Add delay after delayAfter is reached +}); export const baseRateLimiter = rateLimit(baseRateLimitOptions); @@ -69,4 +58,14 @@ export const loginLimiter = rateLimit({ ...baseRateLimitOptions, limit: 3, message: 'Too many attempts without valid login', -}); \ No newline at end of file +}); + + +export function cleanup() { + const oneHourAgo = Date.now() - 60 * 60 * 1000; + for (const ip in ipsThatReachedLimit) { + if (ipsThatReachedLimit[ip].time < oneHourAgo) { + delete ipsThatReachedLimit[ip]; + } + } +} \ No newline at end of file diff --git a/src/middleware/logged-in.ts b/src/middleware/logged-in.ts index eddca045..f6546ba9 100644 --- a/src/middleware/logged-in.ts +++ b/src/middleware/logged-in.ts @@ -1,10 +1,10 @@ import { Request, Response, NextFunction } from 'express'; -import { validateToken } from '@src/scripts/token'; +import { validateJWT } from '@src/scripts/token'; import { create as createError } from '@src/middleware/error'; export function isLoggedIn(req: Request, res: Response, next: NextFunction) { - const result = validateToken(req); + const result = validateJWT(req); if (!result.success) { createError(res, result.status, result.message || "", next) } else { diff --git a/src/scripts/crypt.ts b/src/scripts/crypt.ts index 1928c521..6bef0df6 100644 --- a/src/scripts/crypt.ts +++ b/src/scripts/crypt.ts @@ -16,5 +16,3 @@ function pepper(password: string) { if (!key) { throw new Error('KEYA is not defined in the environment variables'); } return password + crypto.createHmac('sha256', key).digest("base64"); } - - diff --git a/src/scripts/token.ts b/src/scripts/token.ts index f26a7181..f9f4d7ab 100644 --- a/src/scripts/token.ts +++ b/src/scripts/token.ts @@ -1,9 +1,48 @@ import jwt from 'jsonwebtoken'; -import logger from '@src/scripts/logger'; -import {Request, Response } from 'express'; +import { NextFunction, Request, Response } from 'express'; +import crypto from 'crypto'; +import { create as createError } from '@src/middleware/error'; -export function validateToken(req: Request) { +const csrfTokens: Set = new Set(); + +export function createCSRF(res: Response, next: NextFunction): string { + if (csrfTokens.size > 100) { // Max Number of Tokens in memory + res.set('Retry-After', '300'); // 5 minutes + createError(res, 503, "Too many tokens", next); + } + + const token = crypto.randomBytes(16).toString('hex'); + const expiry = Date.now() + (5 * 60 * 1000); // Token expires in 5 minutes + const csrfToken: CSRFToken = { token, expiry }; + csrfTokens.add(csrfToken); + + return token; +} + +export function validateCSRF(token: string): boolean { + const currentTime = Date.now(); + let valid: boolean = false; + for (const entry of csrfTokens) { + if (entry.token === token) { + valid = entry.expiry > currentTime; + csrfTokens.delete(entry); + } + } + + return valid; +} + +export function cleanupCSRF() { + const currentTime = Date.now(); + for (const entry of csrfTokens) { + if (entry.expiry < currentTime) { + csrfTokens.delete(entry); + } + } +} + +export function validateJWT(req: Request) { const key = process.env.KEYA; const header = req.header('Authorization'); const [type, token] = header ? header.split(' ') : ""; @@ -33,7 +72,7 @@ export function validateToken(req: Request) { return { success: true }; } -export function createToken(req: Request, res: Response) { +export function createJWT(req: Request, res: Response) { const key = process.env.KEYA; if (!key) { throw new Error('Configuration is wrong'); } const today = new Date(); @@ -44,6 +83,5 @@ export function createToken(req: Request, res: Response) { }; const token = jwt.sign(payload, key, { expiresIn: 60 * 2 }); res.locals.token = token; - logger.log(JSON.stringify(payload), true); return token; } diff --git a/src/tests/integration.test.ts b/src/tests/integration.test.ts index 4fd5f4e0..067d3eae 100644 --- a/src/tests/integration.test.ts +++ b/src/tests/integration.test.ts @@ -231,10 +231,25 @@ describe('API calls', () => { describe('read and login', () => { let token = ""; - const testData = qs.stringify({ + const testData = { user: "TEST", password: "test", - }); + csrfToken: "" + } + + it('form available / get Token', async () => { + let response = {data:""}; + try { + response = await axios.get('http://localhost:80/login'); + } catch (error) { + console.error(error); + } + + const regex = /name="csrfToken" value="([^"]*)"/; + const match = response.data.match(regex); + testData.csrfToken = match ? match[1] : '-'; + }) + test(`redirect without logged in`, async () => { try { await axios.get("http://localhost:80/read/"); @@ -249,7 +264,7 @@ describe('read and login', () => { }); it('test user can login', async () => { - const response = await axios.post('http://localhost:80/login', testData); + const response = await axios.post('http://localhost:80/login', qs.stringify(testData)); expect(response.status).toBe(200); expect(response.headers['content-type']).toEqual(expect.stringContaining('application/json')); diff --git a/src/tests/login.test.ts b/src/tests/login.test.ts index 6fbc3404..e1778269 100644 --- a/src/tests/login.test.ts +++ b/src/tests/login.test.ts @@ -6,11 +6,18 @@ const userDataLarge = qs.stringify({ password: "pass", kilobyte: 'BPSwVu5vcvhWB17HcfIdyQK83mHJZKChv7zDihBJoifWK9EJFzK7VYf3kUgIqkc0io8DnSdewzc9U0GpzodQUFz0KLMaogsJruEbNSKvxnzUxS5UqSR64lLOmGumoPcn2InC0Ebpqfdiw90HFVZVlE3AY6Lhgbx8ILHi55RvpuGefDjBsePgow8Jh9sc8uVMCDglLmHQ0zk3PumMj0KlOszbMmX9fG0pPUsvLLc40biPBv9t97K3BFjYd3fGriRAQ3bFhGHBz2wzGbNQfHjKFDHuSvXOw8KReM7Wwd4Cl02QQ3RnDJVwH6cayh4BqFRXlP3i6uXw0l9qxdTv0q1CtV9rJho6zwo04gkGLvsS3AoYJQtHnOtUDdHPExu7l3nMKnPoRUwl7K2ePfHRuppFGqa43Q49bI04VjEhrB9k5S2uZJoxZdm63rIUrydmkZWdvBLVVZUIXwwIRnwLmoa26htKOz9FPKwWIPOM0NZj4jAoPhKqLDJwziNZn5UupzxBXoUM3BIyEk3K8GXs7eBduH9GCK2z2HPF0fJNtGiHASe7jCOC2mhSC5zGf9k0Yu1Ey63oQQZUtT7L57lp7UzPE2p6wzKDlbJZOn0Ho5OUfq3hE2C8fQRO1M6jDvRTiUIKhhxSHYd75Pvh4SG9lD8w5OHASusLDxmzKBUuG4GrGrQYpd0awJkqnKp5lk7psLD22YTtjTuDgI500tQLXSslxI1kIuB8RnN1LsxHyRQMVtXmNFOKKZV2U2frWpImIz2wSHCYrwRGygwDtiFfwtVwTapjhQqUMyb1vrWWi3EL1Y50fDCjDDHlvLI4N2tr2DULFf3a9m2SYWSoE6CYP4og5YyqjhqFQFm9urREInyZi9L0iQoMYxEqxTjGiVJfKmaSChSd0kQz6z2OdsxFbkMWJ2CAHOL1XNK8iFFSp93fIspaNMIonRVDCj4ZIP1LaPHDmIYcYTNU4k3Uz6VBHSIc1VjiG3sc2MZpKw9An0tJVlWbtVSk2RGYWIANAYyr5pQS' }); -const userData = qs.stringify({ +const userDataWithoutToken = qs.stringify({ user: "user", password: "pass" }); +let csrfToken = "-"; +const userDataWithToken = { + user: "user", + password: "pass", + csrfToken: "" +}; + describe('Login', () => { it('form available', async () => { let serverStatus = {}; @@ -24,6 +31,10 @@ describe('Login', () => { expect(serverStatus).toBe(200); expect(response.data).toContain(' { @@ -39,13 +50,38 @@ describe('Login', () => { } }) - it('invalid login verification test', async () => { + it('invalid csrf shows correct error', async () => { + try { + await axios.post('http://localhost:80/login', userDataWithoutToken); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(403); + if (axiosError.response.data) { + expect(JSON.stringify(axiosError.response.data)).toContain('Invalid CSRF'); + } else { + throw Error("fail"); + } + } else { + console.error(axiosError); + } + } + }) + + + it('test invalid credentials to return error', async () => { try { - await axios.post('http://localhost:80/login', userData); + userDataWithToken.csrfToken = csrfToken + await axios.post('http://localhost:80/login', qs.stringify(userDataWithToken)); } catch (error) { const axiosError = error as AxiosError; if (axiosError.response) { expect(axiosError.response.status).toBe(403); + if (axiosError.response.data) { + expect(JSON.stringify(axiosError.response.data)).toContain('Invalid credentials'); + } else { + throw Error("fail"); + } } else { console.error(axiosError); } diff --git a/types.d.ts b/types.d.ts index 6bb5b4cc..3cde1ce3 100644 --- a/types.d.ts +++ b/types.d.ts @@ -118,6 +118,11 @@ namespace Models { } } +interface CSRFToken { + token: string; + expiry: number; +} + interface HttpError extends Error { status?: number; statusCode?: number; diff --git a/views/login-form.ejs b/views/login-form.ejs index 802a7c95..34bf5517 100644 --- a/views/login-form.ejs +++ b/views/login-form.ejs @@ -10,7 +10,7 @@ - From 1018d2e10876b92dfe1c916ab938d262fe9a00e5 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 26 Mar 2024 12:35:24 +0100 Subject: [PATCH 59/62] fix: upgrade express-rate-limit from 7.1.5 to 7.2.0 (#52) Snyk has created this PR to upgrade express-rate-limit from 7.1.5 to 7.2.0. See this package in npm: https://www.npmjs.com/package/express-rate-limit See this project in Snyk: https://app.snyk.io/org/type-style/project/e2bcd002-cb74-409c-ba55-fb6349df1cbc?utm_source=github&utm_medium=referral&page=upgrade-pr Co-authored-by: snyk-bot --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index bae40f0e..0cce3861 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "compression": "^1.7.4", "ejs": "^3.1.9", "express": "^4.18.2", - "express-rate-limit": "^7.1.5", + "express-rate-limit": "^7.2.0", "express-slow-down": "^2.0.1", "express-validator": "^7.0.1", "helmet": "^7.1.0", @@ -3711,9 +3711,9 @@ } }, "node_modules/express-rate-limit": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.1.5.tgz", - "integrity": "sha512-/iVogxu7ueadrepw1bS0X0kaRC/U0afwiYRSLg68Ts+p4Dc85Q5QKsOnPS/QUjPMHvOJQtBDrZgvkOzf8ejUYw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.2.0.tgz", + "integrity": "sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==", "engines": { "node": ">= 16" }, diff --git a/package.json b/package.json index d42439dc..cd90a10b 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "compression": "^1.7.4", "ejs": "^3.1.9", "express": "^4.18.2", - "express-rate-limit": "^7.1.5", + "express-rate-limit": "^7.2.0", "express-slow-down": "^2.0.1", "express-validator": "^7.0.1", "helmet": "^7.1.0", From eb76667023e5640d1067d562cffe9b782dbb2a08 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 26 Mar 2024 12:49:50 +0100 Subject: [PATCH 60/62] fix: package.json & package-lock.json to reduce vulnerabilities (#54) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-EXPRESS-6474509 Co-authored-by: snyk-bot --- package-lock.json | 90 +++++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0cce3861..471409b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "chalk": "^4.1.2", "compression": "^1.7.4", "ejs": "^3.1.9", - "express": "^4.18.2", + "express": "^4.19.2", "express-rate-limit": "^7.2.0", "express-slow-down": "^2.0.1", "express-validator": "^7.0.1", @@ -2439,12 +2439,12 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -2452,7 +2452,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -2461,20 +2461,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2917,9 +2903,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -3670,16 +3656,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -4191,9 +4177,9 @@ } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -6199,6 +6185,20 @@ "node": ">= 0.6" } }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -6449,16 +6449,16 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "node_modules/set-function-length": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", - "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.2", + "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6500,13 +6500,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" diff --git a/package.json b/package.json index cd90a10b..e27991c9 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "chalk": "^4.1.2", "compression": "^1.7.4", "ejs": "^3.1.9", - "express": "^4.18.2", + "express": "^4.19.2", "express-rate-limit": "^7.2.0", "express-slow-down": "^2.0.1", "express-validator": "^7.0.1", From ff66e39438c8acad89e3068fec31e770762df7ad Mon Sep 17 00:00:00 2001 From: Type-Style Date: Tue, 26 Mar 2024 12:53:12 +0100 Subject: [PATCH 61/62] [Snyk] Upgrade express from 4.18.2 to 4.18.3 (#51) * fix: upgrade express from 4.18.2 to 4.18.3 Snyk has created this PR to upgrade express from 4.18.2 to 4.18.3. See this package in npm: https://www.npmjs.com/package/express See this project in Snyk: https://app.snyk.io/org/type-style/project/e2bcd002-cb74-409c-ba55-fb6349df1cbc?utm_source=github&utm_medium=referral&page=upgrade-pr * 50 integrate csrf protection for login form (#53) * [Task] #50, create CSRF Validation for login form * [Task] #43, added icon to repository for later use * [Task] #50, cleanup cetntralized; rename token functions * [Task] #50, reduced token length and improved error handling * [Task] #50 csrf tests added to login * [Task] #50, added test case for csrf, repaired integration * fix: upgrade express-rate-limit from 7.1.5 to 7.2.0 (#52) Snyk has created this PR to upgrade express-rate-limit from 7.1.5 to 7.2.0. See this package in npm: https://www.npmjs.com/package/express-rate-limit See this project in Snyk: https://app.snyk.io/org/type-style/project/e2bcd002-cb74-409c-ba55-fb6349df1cbc?utm_source=github&utm_medium=referral&page=upgrade-pr Co-authored-by: snyk-bot --------- Co-authored-by: snyk-bot From 5d616b43b478f65d5363dc8fe2bdfd52fa2e5777 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Thu, 28 Mar 2024 16:37:14 +0100 Subject: [PATCH 62/62] [Task] update dev after main merge --- package-lock.json | 2792 +++++++++++++++++++++++++++++++++- package.json | 6 +- src/app.test.ts | 15 - src/app.ts | 2 +- src/controller/write.test.ts | 100 -- src/models/entry.test.ts | 47 - tsconfig.json | 1 + 7 files changed, 2790 insertions(+), 173 deletions(-) delete mode 100644 src/app.test.ts delete mode 100644 src/controller/write.test.ts delete mode 100644 src/models/entry.test.ts diff --git a/package-lock.json b/package-lock.json index 471409b3..2953ac79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,14 +24,14 @@ }, "devDependencies": { "@jest/globals": "^29.7.0", - "@tsconfig/node20": "^20.1.2", + "@tsconfig/node20": "^20.1.4", "@types/bcrypt": "^5.0.2", "@types/compression": "^1.7.5", "@types/express": "^4.17.21", "@types/hpp": "^0.2.5", "@types/jest": "^29.5.11", "@types/jsonwebtoken": "^9.0.6", - "@types/node": "^20.10.6", + "@types/node": "^20.11.30", "@types/toobusy-js": "^0.5.4", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", @@ -40,8 +40,10 @@ "dotenv": "^16.3.1", "eslint": "^8.56.0", "eslint-plugin-jest": "^27.6.3", + "install": "^0.13.0", "jest": "^29.7.0", "nodemon": "^3.0.2", + "npm": "^10.5.0", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.3.3" @@ -1525,9 +1527,9 @@ "dev": true }, "node_modules/@tsconfig/node20": { - "version": "20.1.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.2.tgz", - "integrity": "sha512-madaWq2k+LYMEhmcp0fs+OGaLFk0OenpHa4gmI4VEmCKX4PJntQ6fnnGADVFrVkBj0wIdAlQnK/MrlYTHsa1gQ==", + "version": "20.1.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", + "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", "dev": true }, "node_modules/@types/babel__core": { @@ -1712,9 +1714,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.10.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz", - "integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==", + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -4381,6 +4383,15 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/install": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -5734,6 +5745,163 @@ "node": ">=0.10.0" } }, + "node_modules/npm": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.5.0.tgz", + "integrity": "sha512-Ejxwvfh9YnWVU2yA5FzoYLTW52vxHCz+MHrOFg9Cc8IFgF/6f5AGPAvb5WTay5DIUP1NIfN3VBZ0cLlGO0Ys+A==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "cli-table3", + "columnify", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "npmlog", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^7.2.1", + "@npmcli/config": "^8.0.2", + "@npmcli/fs": "^3.1.0", + "@npmcli/map-workspaces": "^3.0.4", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.1", + "@npmcli/run-script": "^7.0.4", + "@sigstore/tuf": "^2.3.1", + "abbrev": "^2.0.0", + "archy": "~1.0.0", + "cacache": "^18.0.2", + "chalk": "^5.3.0", + "ci-info": "^4.0.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.3", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^7.0.1", + "ini": "^4.1.1", + "init-package-json": "^6.0.0", + "is-cidr": "^5.0.3", + "json-parse-even-better-errors": "^3.0.1", + "libnpmaccess": "^8.0.1", + "libnpmdiff": "^6.0.3", + "libnpmexec": "^7.0.4", + "libnpmfund": "^5.0.1", + "libnpmhook": "^10.0.0", + "libnpmorg": "^6.0.1", + "libnpmpack": "^6.0.3", + "libnpmpublish": "^9.0.2", + "libnpmsearch": "^7.0.0", + "libnpmteam": "^6.0.0", + "libnpmversion": "^5.0.1", + "make-fetch-happen": "^13.0.0", + "minimatch": "^9.0.3", + "minipass": "^7.0.4", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^10.0.1", + "nopt": "^7.2.0", + "normalize-package-data": "^6.0.0", + "npm-audit-report": "^5.0.0", + "npm-install-checks": "^6.3.0", + "npm-package-arg": "^11.0.1", + "npm-pick-manifest": "^9.0.0", + "npm-profile": "^9.0.0", + "npm-registry-fetch": "^16.1.0", + "npm-user-validate": "^2.0.0", + "npmlog": "^7.0.1", + "p-map": "^4.0.0", + "pacote": "^17.0.6", + "parse-conflict-json": "^3.0.1", + "proc-log": "^3.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^2.1.0", + "semver": "^7.6.0", + "spdx-expression-parse": "^3.0.1", + "ssri": "^10.0.5", + "supports-color": "^9.4.0", + "tar": "^6.2.0", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^5.0.0", + "which": "^4.0.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -5746,6 +5914,2614 @@ "node": ">=8" } }, + "node_modules/npm/node_modules/@colors/colors": { + "version": "1.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "7.4.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.0", + "@npmcli/installed-package-contents": "^2.0.2", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^7.0.0", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/query": "^3.1.0", + "@npmcli/run-script": "^7.0.2", + "bin-links": "^4.0.1", + "cacache": "^18.0.0", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^7.0.1", + "json-parse-even-better-errors": "^3.0.0", + "json-stringify-nice": "^1.1.4", + "minimatch": "^9.0.0", + "nopt": "^7.0.0", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^11.0.1", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "npmlog": "^7.0.1", + "pacote": "^17.0.4", + "parse-conflict-json": "^3.0.0", + "proc-log": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.5", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "8.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^3.0.2", + "ci-info": "^4.0.0", + "ini": "^4.1.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ansi-styles": "^4.3.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "5.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^18.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^17.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.3.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "2.2.3", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "2.3.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.0", + "tuf-js": "^2.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/models": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/are-we-there-yet": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/builtins": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "18.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.0.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cli-table3": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/npm/node_modules/clone": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/npm/node_modules/columnify": { + "version": "1.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/console-control-strings": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/defaults": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.2.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/gauge": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^4.0.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.3.10", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/has-unicode": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hasown": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "6.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^11.0.0", + "promzard": "^1.0.0", + "read": "^2.0.0", + "read-package-json": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "4.0.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-core-module": { + "version": "2.13.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "2.3.6", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^11.0.1", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "6.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/disparity-colors": "^3.0.0", + "@npmcli/installed-package-contents": "^2.0.2", + "binary-extensions": "^2.2.0", + "diff": "^5.1.0", + "minimatch": "^9.0.0", + "npm-package-arg": "^11.0.1", + "pacote": "^17.0.4", + "tar": "^6.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "7.0.8", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/run-script": "^7.0.2", + "ci-info": "^4.0.0", + "npm-package-arg": "^11.0.1", + "npmlog": "^7.0.1", + "pacote": "^17.0.4", + "proc-log": "^3.0.0", + "read": "^2.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "5.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "10.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "6.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/run-script": "^7.0.2", + "npm-package-arg": "^11.0.1", + "pacote": "^17.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "9.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^6.0.0", + "npm-package-arg": "^11.0.1", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.7", + "sigstore": "^2.2.0", + "ssri": "^10.0.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "5.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.3", + "@npmcli/run-script": "^7.0.2", + "json-parse-even-better-errors": "^3.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "13.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-json-stream": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "10.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "7.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "6.3.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "11.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "16.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npmlog": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^4.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^5.0.0", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "17.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^7.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^7.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.10.1", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.0.15", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~1.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.6.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "2.2.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0", + "@sigstore/sign": "^2.2.3", + "@sigstore/tuf": "^2.3.1", + "@sigstore/verify": "^1.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 16.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.17", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "10.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "2.0.0", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", diff --git a/package.json b/package.json index e27991c9..4e3216f7 100644 --- a/package.json +++ b/package.json @@ -24,14 +24,14 @@ "author": "Type-Style", "devDependencies": { "@jest/globals": "^29.7.0", - "@tsconfig/node20": "^20.1.2", + "@tsconfig/node20": "^20.1.4", "@types/bcrypt": "^5.0.2", "@types/compression": "^1.7.5", "@types/express": "^4.17.21", "@types/hpp": "^0.2.5", "@types/jest": "^29.5.11", "@types/jsonwebtoken": "^9.0.6", - "@types/node": "^20.10.6", + "@types/node": "^20.11.30", "@types/toobusy-js": "^0.5.4", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", @@ -40,8 +40,10 @@ "dotenv": "^16.3.1", "eslint": "^8.56.0", "eslint-plugin-jest": "^27.6.3", + "install": "^0.13.0", "jest": "^29.7.0", "nodemon": "^3.0.2", + "npm": "^10.5.0", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.3.3" diff --git a/src/app.test.ts b/src/app.test.ts deleted file mode 100644 index 0682f2d3..00000000 --- a/src/app.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import axios from 'axios'; - -describe('Server Status', () => { - it('The server is running', async () => { - let serverStatus; - try { - const response = await axios.get('http://localhost'); - serverStatus = response.status; - } catch (error) { - console.error(error); - } - - expect(serverStatus).toBe(200); - }) -}) \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index b11cfdb6..17afecb7 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,6 +1,7 @@ require('module-alias/register'); import { config } from 'dotenv'; import express from 'express'; +import path from 'path'; import toobusy from 'toobusy-js'; import compression from 'compression'; import helmet from 'helmet'; @@ -10,7 +11,6 @@ import * as error from "./middleware/error"; import writeRouter from '@src/controller/write'; import readRouter from '@src/controller/read'; import loginRouter from '@src/controller/login'; -import path from 'path'; import logger from '@src/scripts/logger'; import { baseRateLimiter, cleanup as cleanupRateLimitedIps } from './middleware/limit'; import { cleanupCSRF } from "@src/scripts/token"; diff --git a/src/controller/write.test.ts b/src/controller/write.test.ts deleted file mode 100644 index f447d22d..00000000 --- a/src/controller/write.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import axios, { AxiosError } from 'axios'; - -describe('HEAD /write', () => { - it('with all parameters correctly set it should succeed', async () => { - const timestamp = new Date().getTime(); - const response = await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - expect(response.status).toBe(200); - }); - - it('without key it sends 403', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(403); - } - }); - - it('with user length not equal to 2 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=x&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - - it('with lat not between -90 and 90 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=91.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with lon not between -180 and 180 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=181.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with timestamp to old sends 422', async () => { - try { - const timestamp = new Date().getTime() - 24 * 60 * 60 * 1000 * 2; // two days ago - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }) - - it('with hdop not between 0 and 100 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with altitude not between 0 and 10000 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with speed not between 0 and 300 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with heading not between 0 and 360 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); -}); diff --git a/src/models/entry.test.ts b/src/models/entry.test.ts deleted file mode 100644 index bd2c0b4d..00000000 --- a/src/models/entry.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { checkNumber, checkTime } from "./entry"; - - -describe("checkNumber", () => { - it("should throw error if value is not provided", () => { - expect(() => checkNumber(0, 100)("")).toThrow(new Error('is required')); - }); - - it("should throw error if value length is more than 12", () => { - expect(() => checkNumber(0, 100)("1234567890123")).toThrow(new Error('Should have a maximum of 11 digits')); - }); - - it("should throw error if value is not a number", () => { - expect(() => checkNumber(0, 100)("abc")).toThrow(new Error('Value should be between 0 and 100')); - }); - - it("should return true if value is a valid number within range", () => { - expect(checkNumber(0, 100)("50")).toBe(true); - }); -}); - -describe("checkTime", () => { - it("should throw error if value is not a number", () => { - expect(() => checkTime("abc")).toThrow(new Error('Timestamp should be a number')); - }); - - it("should throw error if value is not a valid date", () => { - expect(() => checkTime("99999999999999999999")).toThrow(new Error('Timestamp should represent a valid date')); - }); - - it("should throw error if value is more than 1 day in the past", () => { - const date = new Date(); - date.setDate(date.getDate() - 2); // Set date to 2 days ago - expect(() => checkTime(date.getTime().toString())).toThrow(new Error('Timestamp should represent a date not further from server time than 1 day')); - }); - - it("should throw error if value is more than 1 day in the future", () => { - const date = new Date(); - date.setDate(date.getDate() + 2); // Set date to 2 days in the future - expect(() => checkTime(date.getTime().toString())).toThrow(new Error('Timestamp should represent a date not further from server time than 1 day')); - }); - - it("should return true if value is a valid timestamp within 1 day", () => { - const date = new Date(); - expect(checkTime(date.getTime().toString())).toBe(true); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index 613bc082..5f168cdf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "target": "ES6", "sourceMap": true, "baseUrl": "./src", + "esModuleInterop": true, "paths": { "@src/*": ["./*"], }