commit d134b480a0b7ea888ae8b10d8f5157081ea3d02d Author: burkkyy Date: Wed Jun 24 23:47:55 2026 -0700 Initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..20339ed --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +api/node_modules +api/dist +web/node_modules +web/dist +dist +npm-debug.log +node_modules +docs + +/now.* +/*.now.* +**/now.* diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..3290a2b --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,11 @@ +env: + browser: true + es2021: true + node: true +extends: + - prettier +overrides: [] +parserOptions: + ecmaVersion: latest + sourceType: module +rules: {} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d51e049 --- /dev/null +++ b/.gitignore @@ -0,0 +1,112 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories + files +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env.production + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist +app + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port +db/sapassword.env +.env.development + +# DB Data +db/data +/db_dumps diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..e9cd27d --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,19 @@ +# .prettierrc or .prettierrc.yaml +$schema: "https://json.schemastore.org/prettierrc" +embeddedLanguageFormatting: "auto" +trailingComma: "es5" +tabWidth: 2 +semi: false +singleQuote: false +singleAttributePerLine: true +useTabs: false +endOfLine: "auto" +printWidth: 100 +plugins: + - prettier-plugin-embed + - prettier-plugin-sql +overrides: + - files: "*.sql" + options: + language: "tsql" + paramTypes: "{ named: [':'] }" diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..4ed1c6a --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +nodejs 20.10.0 +ruby 3.2.2 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0face08 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "eslint.workingDirectories": ["./api", "./web"], + "eslint.validate": ["javascript", "typescript", "vue"], + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "prettier.configPath": ".prettierrc.yaml", + "prettier.requireConfig": true, + "prettier.resolveGlobalModules": true, + "js/ts.preferences.importModuleSpecifier": "non-relative", + "[vue]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d8d3983 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,77 @@ +# Stage 0 - base node customizations +FROM node:20.19.4-alpine3.22 AS base-node + +RUN npm install -g npm@10.2.5 + +# Stage 1 - api build - requires development environment because typescript +FROM base-node AS api-build-stage + +ENV NODE_ENV=development + +WORKDIR /usr/src/api + +COPY api/package*.json ./ +COPY api/tsconfig*.json ./ +RUN npm install + +COPY api ./ + +RUN npm run build + +# Copy html files, remove once we are using Vite for build process +COPY api/src/templates ./dist/templates + +# Stage 2 - web build - requires development environment to install vue-cli-service +FROM base-node AS web-build-stage + +ENV NODE_ENV=development + +WORKDIR /usr/src/web + +COPY web/package*.json ./ +COPY web/tsconfig*.json ./ +COPY web/vite.config.js ./ +RUN npm install + +COPY web ./ + +# Switching to production mode for build environment. +ENV NODE_ENV=production +RUN npm run build + +# Stage 3 - production setup +FROM base-node + +ARG RELEASE_TAG +ARG GIT_COMMIT_HASH + +ENV RELEASE_TAG=${RELEASE_TAG} +ENV GIT_COMMIT_HASH=${GIT_COMMIT_HASH} + +# Persists TZ=UTC effect after container build and into container run +# Ensures dates/times are consistently formated as UTC +# Conversion to local time should happen in the UI +ENV TZ=UTC + +ENV NODE_ENV=production +USER node + +WORKDIR /home/node/app +RUN chown -R node:node /home/node/app + +COPY --from=api-build-stage --chown=node:node /usr/src/api/package*.json ./ +RUN npm install && npm cache clean --force --loglevel=error + +COPY --from=api-build-stage --chown=node:node /usr/src/api/dist ./dist/ +COPY --from=web-build-stage --chown=node:node /usr/src/web/dist ./dist/web/ + +RUN echo "RELEASE_TAG=${RELEASE_TAG}" >> VERSION +RUN echo "GIT_COMMIT_HASH=${GIT_COMMIT_HASH}" >> VERSION + +EXPOSE 3000 + +COPY --from=api-build-stage --chown=node:node /usr/src/api/bin/boot-app.sh ./bin/ + +RUN chmod +x ./bin/boot-app.sh + +CMD ["./bin/boot-app.sh"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f092cb --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# alphane.ca diff --git a/api/.eslintrc.cjs b/api/.eslintrc.cjs new file mode 100644 index 0000000..5d9801c --- /dev/null +++ b/api/.eslintrc.cjs @@ -0,0 +1,28 @@ +/* eslint-env node */ + +// https://github.com/typescript-eslint/typescript-eslint/issues/251 +module.exports = { + root: true, + env: { + es2020: true, + node: true, + }, + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], + overrides: [], + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint"], + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + ignoreRestSiblings: true, + }, + ], + }, +} diff --git a/api/bin/boot-app.sh b/api/bin/boot-app.sh new file mode 100755 index 0000000..d1492bc --- /dev/null +++ b/api/bin/boot-app.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +if [ "$RUN_SCHEDULER" = "true" ]; then + echo "Running scheduler" + if [ "$NODE_ENV" = "production" ]; then + node ./dist/scheduler.js + else + npm run start:scheduler + fi + exit 0 +fi + +if [ "$NODE_ENV" = "production" ]; then + node ./dist/initializers/index.js + node ./dist/server.js +else + npm run initializers + npm run start +fi diff --git a/api/development.Dockerfile b/api/development.Dockerfile new file mode 100644 index 0000000..725fc5e --- /dev/null +++ b/api/development.Dockerfile @@ -0,0 +1,14 @@ +FROM node:24.15.0-alpine3.23 + +RUN npm i -g npm@11.14.1 + +WORKDIR /usr/src/api + +COPY package*.json ./ + +RUN npm clean-install + +COPY bin/boot-app.sh ./bin/ +RUN chmod +x ./bin/boot-app.sh + +CMD ["/usr/src/api/bin/boot-app.sh"] diff --git a/api/package-lock.json b/api/package-lock.json new file mode 100644 index 0000000..3a01ce7 --- /dev/null +++ b/api/package-lock.json @@ -0,0 +1,8134 @@ +{ + "name": "alphane-api", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "alphane-api", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "@fontsource/audiowide": "^5.2.7", + "@sequelize/core": "^7.0.0-alpha.40", + "@sequelize/postgres": "^7.0.0-alpha.46", + "axios": "^1.7.3", + "cors": "^2.8.5", + "cron": "^4.4.0", + "dotenv": "^17.2.1", + "express": "^5.1.0", + "express-form-data": "^3.0.1", + "express-jwt": "^8.4.1", + "geist": "^1.7.0", + "helmet": "^8.1.0", + "jwks-rsa": "^3.1.0", + "knex": "^3.1.0", + "lodash": "^4.17.21", + "luxon": "^3.5.0", + "morgan": "^1.10.0", + "nodemailer": "^7.0.5", + "papaparse": "^5.4.1", + "redis": "^5.8.2", + "reflect-metadata": "^0.2.2", + "winston": "^3.17.0" + }, + "devDependencies": { + "@faker-js/faker": "^9.9.0", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.3", + "@types/express-form-data": "^2.0.5", + "@types/lodash": "^4.17.13", + "@types/luxon": "^3.4.2", + "@types/morgan": "^1.9.9", + "@types/node-schedule": "^2.1.5", + "@types/nodemailer": "^7.0.1", + "@types/papaparse": "^5.3.15", + "@types/sharp": "^0.31.1", + "@types/supertest": "^6.0.2", + "@typescript-eslint/eslint-plugin": "^8.40.0", + "@typescript-eslint/parser": "^8.40.0", + "axios-mock-adapter": "^2.1.0", + "eslint": "^8.57.1", + "eslint-config-prettier": "^10.1.8", + "fishery": "^2.2.2", + "prettier": "^3.1.1", + "supertest": "^7.1.4", + "ts-node": "^10.9.2", + "ts-node-dev": "^2.0.0", + "tsc-alias": "^1.8.8", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.6.3", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.2.4" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "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, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "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": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/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, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@faker-js/faker": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.9.0.tgz", + "integrity": "sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } + }, + "node_modules/@fontsource/audiowide": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@fontsource/audiowide/-/audiowide-5.2.7.tgz", + "integrity": "sha512-ude+jAqXaEkV4vfKAYyfub694LSy/OKkYyUHumYrEP3ZgSSP6TomITFLUC8x6Fhc2salL7nuju3xT6aWOHECPg==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/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, + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@next/env": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.9.tgz", + "integrity": "sha512-ki5VxxXfzD/9TDe13wyeTKIjQTAwBVpnr8KhRDUr8ltMUq1/NBpWNT5tiPoxiGl+PHM4X2ahSOiPk6iAimIzPg==", + "license": "MIT", + "peer": true + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.9.tgz", + "integrity": "sha512-HkfxNYUCmcct0Xsqib5KxqMSHV4AHJq857BNRchyBDs4YS19aHzVfn1kDuBYKqLLQBjXgnkIsjV2Kd4d2wzYhw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.9.tgz", + "integrity": "sha512-7IAtK4MeybpqRV9GRABWEhJ62mOS+rzWOzOTFie4cSEtm12xsoOMJRcECoZx3FHPzFAqN/IJtHqWAFOLfl152w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.9.tgz", + "integrity": "sha512-hBD75iWpUtkL9SmQmcRhmLomn9jgkPzCEkbOcLgHymPEKzv+6ONy13RRiIEz/iEObjkS2Jlb5gYS2XGoS3X4rw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.9.tgz", + "integrity": "sha512-qZTI3pf9SGc/obr8NkQAekBxmp1QK+kVm+VAf3BALLfFAj+1kUhkTxmrWpVos9R/UYIA8AWX2p6cGI5WdwzVUA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.9.tgz", + "integrity": "sha512-xm0HfRNX+UkH4R3c18ynswjj5o5uEj/7iI9p9omdtTSIsRCzQqkGMA+10nzJ4EHnYC3as65IMhbbl5fWRUWHYg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.9.tgz", + "integrity": "sha512-QumimHkGEG6vM3PfEDWKyKen03NcqLOkeKB1EfcPe7VxzmEiCa4jNnMyBn/US5zcd/VE1CI+O8Ovb3lfjVHfGw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.9.tgz", + "integrity": "sha512-hzQpKZvw8rAwI6A2uQh6SacCSvNAXaIkPNsWwzqqfRiIMiXMfH936skDhz1OO6KpvdKkJrgHHtqQOq5PIXOvdQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.9.tgz", + "integrity": "sha512-qr2VL3Ce5QrwgO2yh1ujSBawrimjVKX8FGF/cOynmdYKJY0BdHpGVNIRK1tqONB10Vkm25Ub1BD2bkjWs4+96w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@redis/bloom": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.12.1.tgz", + "integrity": "sha512-PUUfv+ms7jgPSBVoo/DN4AkPHj4D5TZSd6SbJX7egzBplkYUcKmHRE8RKia7UtZ8bSQbLguLvxVO+asKtQfZWA==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/client": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.12.1.tgz", + "integrity": "sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@node-rs/xxhash": "^1.1.0", + "@opentelemetry/api": ">=1 <2" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@redis/json": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.12.1.tgz", + "integrity": "sha512-eOze75esLve4vfqDel7aMX08CNaiLLQS2fV8mpRN9NxPe1rVR4vQyYiW/OgtGUysF6QOr9ANhfxABKNOJfXdKg==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/search": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.12.1.tgz", + "integrity": "sha512-ItlxbxC9cKI6IU1TLWoczwJCRb6TdmkEpWv05UrPawqaAnWGRu3rcIqsc5vN483T2fSociuyV1UkWIL5I4//2w==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/time-series": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.12.1.tgz", + "integrity": "sha512-c6JL6E3EcZJuNqKFz+KM+l9l5mpcQiKvTwgA3blt5glWJ8hjDk0yeHN3beE/MpqYIQ8UEX44ItQzgkE/gCBELQ==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sequelize/core": { + "version": "7.0.0-alpha.48", + "resolved": "https://registry.npmjs.org/@sequelize/core/-/core-7.0.0-alpha.48.tgz", + "integrity": "sha512-jRqJlgPo/H6qKpQY8ws+ULebRVtcvo/DDVBgf49Cn1Yd41xk+yZx3izuI9gLlZQQ75B/PRIbcP9WrHWAD8tJkg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/sequelize" + } + ], + "license": "MIT", + "dependencies": { + "@sequelize/utils": "7.0.0-alpha.48", + "@types/debug": "^4.1.12", + "@types/validator": "^13.15.10", + "ansis": "^3.17.0", + "bnf-parser": "^3.1.6", + "dayjs": "^1.11.19", + "debug": "^4.4.3", + "dottie": "^2.0.6", + "fast-glob": "^3.3.3", + "inflection": "^3.0.2", + "lodash": "^4.17.23", + "retry-as-promised": "^7.1.1", + "semver": "^7.7.3", + "sequelize-pool": "^8.0.1", + "toposort-class": "^1.0.1", + "type-fest": "^4.41.0", + "uuid": "^11.1.0", + "validator": "^13.15.26" + }, + "engines": { + "node": ">=18.20.8" + } + }, + "node_modules/@sequelize/postgres": { + "version": "7.0.0-alpha.48", + "resolved": "https://registry.npmjs.org/@sequelize/postgres/-/postgres-7.0.0-alpha.48.tgz", + "integrity": "sha512-Di7XMGDfK4H7Ne2e9WbfjriCyaRVauOe2wLITEQPv4w0Tnq5gmU28i1wJKkE6qjegkivz9X8Djm1KbDRZrBiGw==", + "license": "MIT", + "dependencies": { + "@sequelize/core": "7.0.0-alpha.48", + "@sequelize/utils": "7.0.0-alpha.48", + "@types/pg": "^8.11.14", + "lodash": "^4.17.23", + "pg": "^8.15.6", + "pg-hstore": "^2.3.4", + "pg-types": "^4.1.0", + "postgres-array": "^3.0.4", + "semver": "^7.7.3", + "wkx": "^0.5.0" + } + }, + "node_modules/@sequelize/utils": { + "version": "7.0.0-alpha.48", + "resolved": "https://registry.npmjs.org/@sequelize/utils/-/utils-7.0.0-alpha.48.tgz", + "integrity": "sha512-6s7YIDIRULddXuvdm4h6hbZtS6hXkqRzRzeG3WlzICmpY50ju0EC/4Ho3Ek4pYy7gl0IAs4HSmuOrmoUciT6Ng==", + "license": "MIT", + "dependencies": { + "@types/lodash": "^4.17.23", + "lodash": "^4.17.23" + }, + "engines": { + "node": ">=18.20.8" + } + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "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, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-form-data": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/express-form-data/-/express-form-data-2.0.5.tgz", + "integrity": "sha512-zn1Cvy/scDOgV4j5pUKxsPQQVIX5vAGL6xjLWteSApd3HAmmfbdeZ3WMbFAks0kIaAxbSIT79W9GB4AVkS6fOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/multiparty": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "node_modules/@types/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-gW+Oib+vUtGJBtNC8V9Reww0oIpusw+4m81uncg9REGZAJfqOQHfo/nkabnc7w0QReXyPqjrbWMJk6NuAkiX3Q==", + "license": "MIT" + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/morgan": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", + "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/multiparty": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@types/multiparty/-/multiparty-4.2.1.tgz", + "integrity": "sha512-Wi6aK3FgvHLvCDxD7ngG4w8MsCK9h64EB53Gvc8t7FVX81tleiz8vFS3ebBohGxqHRzNGHaNwhfdxTGOGAXm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", + "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/node-schedule": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-2.1.8.tgz", + "integrity": "sha512-k00g6Yj/oUg/CDC+MeLHUzu0+OFxWbIqrFfDiLi6OPKxTujvpv29mHGM8GtKr7B+9Vv92FcK/8mRqi1DK5f3hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/nodemailer": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.12.tgz", + "integrity": "sha512-80vKwiIsVSyFA1rRovH59jNPLBOuc6dRZIHEu40gXTkBkZnQv8vog1xSGEb9j5q/tdMAs5ivvDR2pLTU0hGHXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/papaparse": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz", + "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pg/node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@types/pg/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@types/pg/node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@types/pg/node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@types/pg/node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sharp": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz", + "integrity": "sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/superagent": { + "version": "8.1.10", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.10.tgz", + "integrity": "sha512-nbt4IWXABhW0jGmmpRzCFNlbmwCTzZ2gTUsNIr+X+ItdqPms+PAJZbWsNzpS2USqXjcoNLQcO6nXo60zcPQiIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", + "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/type-utils": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.61.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", + "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", + "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.61.1", + "@typescript-eslint/types": "^8.61.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", + "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", + "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", + "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", + "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", + "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.61.1", + "@typescript-eslint/tsconfig-utils": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", + "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", + "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitest/expect": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", + "integrity": "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.6.tgz", + "integrity": "sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.6.tgz", + "integrity": "sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.6.tgz", + "integrity": "sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.6", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.6.tgz", + "integrity": "sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.6.tgz", + "integrity": "sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.6.tgz", + "integrity": "sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "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==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "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-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "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, + "license": "ISC", + "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, + "license": "MIT" + }, + "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, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/axios": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", + "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios-mock-adapter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-2.1.0.tgz", + "integrity": "sha512-AZUe4OjECGCNNssH8SOdtneiQELsqTsat3SQQCWLPjN436/H+L9AjWfV7bF+Zg/YL9cgbhrz5671hoh+Tbn98w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" + }, + "peerDependencies": { + "axios": ">= 0.17.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/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==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bnf-parser": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/bnf-parser/-/bnf-parser-3.1.6.tgz", + "integrity": "sha512-3x0ECh6CghmcAYnY6uiVAOfl263XkWffDq5fQS20ac3k0U7TE5rTWNXTnOTckgmfZc94iharwqCyoV8OAYxYoA==", + "license": "ISC" + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "BSD-3-Clause" + }, + "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, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "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" + } + ], + "license": "CC-BY-4.0", + "peer": true + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "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" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT", + "peer": true + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "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, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-string/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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, + "license": "MIT" + }, + "node_modules/cron": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-4.4.0.tgz", + "integrity": "sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==", + "license": "MIT", + "dependencies": { + "@types/luxon": "~3.7.0", + "luxon": "~3.7.0" + }, + "engines": { + "node": ">=18.x" + }, + "funding": { + "type": "ko-fi", + "url": "https://ko-fi.com/intcreator" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "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, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dottie": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.7.tgz", + "integrity": "sha512-7lAK2A0b3zZr3UC5aE69CPdCFR4RHW1o2Dr74TqFykxkUCBXSRJum/yPc7g8zRHJqWKomPLHwFLLoUnn8PXXRg==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + } + }, + "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==", + "license": "Apache-2.0", + "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==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@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-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "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, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "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/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "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, + "license": "BSD-2-Clause", + "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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/express-form-data/-/express-form-data-3.0.1.tgz", + "integrity": "sha512-olpFQnvwnQPUoE48X0eiv7WJDThAXS2jnFuhkViDkTECKdEFH+Vga3wghVyCbX67otcRziJb5qmJBEHi1EDm3A==", + "license": "MIT", + "dependencies": { + "fs-extra": "^9.1.0", + "http-errors": "^2.0.0", + "multiparty": "^4.2.3", + "on-finished": "^2.4.1", + "signal-exit": "^3.0.7", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=10.7.0" + } + }, + "node_modules/express-jwt": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-8.5.1.tgz", + "integrity": "sha512-Dv6QjDLpR2jmdb8M6XQXiCcpEom7mK8TOqnr0/TngDKsG2DHVkO8+XnVxkJVN7BuS1I3OrGw6N8j5DaaGgkDRQ==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "^9", + "express-unless": "^2.1.3", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/express-unless": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz", + "integrity": "sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ==", + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "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.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fishery": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fishery/-/fishery-2.4.0.tgz", + "integrity": "sha512-QgeTlvgNhVGuMztrfAhlSIBs3rD3l9RMjl9I15yb/lnrx3njrOhvegr2L3LWdqvXwYfQjdQGpglyAfHH2J8DRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.mergewith": "^4.6.2" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "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, + "license": "ISC" + }, + "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, + "license": "MIT", + "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/geist": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/geist/-/geist-1.7.2.tgz", + "integrity": "sha512-Gu5lDFa3pLRyoBlBPf0QIFHVdWAnpco7fS1bJm41jyLPFoguBgiubseUN2oLXMgqZ7uxAxDoXcHMhCY/fOTTgg==", + "license": "SIL OPEN FONT LICENSE", + "peerDependencies": { + "next": ">=13.2.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/getopts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", + "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "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": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/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, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "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, + "license": "MIT", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.2.0.tgz", + "integrity": "sha512-DRgTIUgnWcJ62KyarxxziuqYxKGnR6Rgg19BlbucN/dpmJbl1XOit6qvoOX0ZT+HhWe5OUVhU/a1zpGyc1xA0Q==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/EvanHahn" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflection": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-3.0.2.tgz", + "integrity": "sha512-+Bg3+kg+J6JUWn8J6bzFmOWkTQ6L/NHfDRSYU+EVvuKHDxUDHAXgqixHfVlzuBQaPOTac8hn43aPhMNk6rMe3g==", + "license": "MIT", + "engines": { + "node": ">=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==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "license": "MIT", + "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", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "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, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.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==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "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/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.2.tgz", + "integrity": "sha512-BqTyEDV+lS8F2trk3A+qJnxV5Q9EqKCBJOPti3W97r7qTympCZjb7h2X6f2kc+0K3rsSTY1/6YG2eaXKoj497w==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "^9.0.4", + "debug": "^4.3.4", + "jose": "^4.15.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/knex": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/knex/-/knex-3.2.10.tgz", + "integrity": "sha512-oypTHfrc9i72iyxaUQBKHOxhcr0xM65MPf6FpN02nimsftXwzXprIkLjfXdubvhbu4PMWLp023q8o8CYvHSuZw==", + "license": "MIT", + "dependencies": { + "colorette": "2.0.19", + "commander": "^10.0.0", + "debug": "4.3.4", + "escalade": "^3.1.1", + "esm": "^3.2.25", + "get-package-type": "^0.1.0", + "getopts": "2.3.0", + "interpret": "^2.2.0", + "lodash": "^4.18.1", + "pg-connection-string": "2.6.2", + "rechoir": "^0.8.0", + "resolve-from": "^5.0.0", + "tarn": "^3.0.2", + "tildify": "2.0.0" + }, + "bin": { + "knex": "bin/cli.js" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "pg-query-stream": "^4.14.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "mysql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/knex/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "license": "MIT", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "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, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/morgan": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.11.0.tgz", + "integrity": "sha512-zSkVu3t18r39pw4ixfBKvfZi3y2UOqr7d4WYwcj3m8nXpEQK4rPO6GLzs/CExoRgmX3y9EjmmcXqv6jq0SK46g==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.4.1", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/multiparty": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.3.0.tgz", + "integrity": "sha512-LD3YDFI9KrDoOGHsPM+hNraPDQQDPLe8Un/kvJfsZCsHKriA4mphg6Ctc2Cuup/59DtHMdAPm6ICXlUmhwTiug==", + "license": "MIT", + "dependencies": { + "http-errors": "2.0.0", + "safe-buffer": "5.2.1", + "uid-safe": "2.1.5" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/multiparty/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==", + "license": "MIT", + "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/multiparty/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/mylas": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz", + "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, + "node_modules/nanoid": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.13.tgz", + "integrity": "sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.9.tgz", + "integrity": "sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww==", + "license": "MIT", + "peer": true, + "dependencies": { + "@next/env": "16.2.9", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.2.9", + "@next/swc-darwin-x64": "16.2.9", + "@next/swc-linux-arm64-gnu": "16.2.9", + "@next/swc-linux-arm64-musl": "16.2.9", + "@next/swc-linux-x64-gnu": "16.2.9", + "@next/swc-linux-x64-musl": "16.2.9", + "@next/swc-win32-arm64-msvc": "16.2.9", + "@next/swc-win32-x64-msvc": "16.2.9", + "sharp": "^0.34.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/nodemailer": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", + "integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/papaparse": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.4.tgz", + "integrity": "sha512-SwzWD9gl/ElwYLCI0nUja1mFJzjq2D8ziShfNBa7zCHzkOozeOGDwHWQ+tvCzEZcewecWZ5U7kUopDnG+DFYEQ==", + "license": "MIT" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pg": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz", + "integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.14.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.15.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==", + "license": "MIT" + }, + "node_modules/pg-hstore": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/pg-hstore/-/pg-hstore-2.3.4.tgz", + "integrity": "sha512-N3SGs/Rf+xA1M2/n0JBiXFDVMzdekwLZLAO0g7mpDY9ouX+fDI7jS6kTq3JujmYbtNSJ53TJ0q4G98KVZSM4EA==", + "license": "MIT", + "dependencies": { + "underscore": "^1.13.1" + }, + "engines": { + "node": ">= 0.8.x" + } + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.15.0.tgz", + "integrity": "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.1.0.tgz", + "integrity": "sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.1.0", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pg/node_modules/pg-connection-string": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz", + "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==", + "license": "MIT" + }, + "node_modules/pg/node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pg/node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pg/node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pg/node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "license": "MIT", + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-range": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "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==", + "license": "MIT", + "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", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/redis": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.12.1.tgz", + "integrity": "sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "5.12.1", + "@redis/client": "5.12.1", + "@redis/json": "5.12.1", + "@redis/search": "5.12.1", + "@redis/time-series": "5.12.1" + }, + "engines": { + "node": ">= 18.19.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz", + "integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==", + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT", + "peer": true + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/sequelize-pool": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-8.0.1.tgz", + "integrity": "sha512-SewZFUa5/OYHml+bD7l1c6lGn6TQCZFir7T5mIn0ruEBvadN5lZ9T7hTn+u/clvsu2h1OzBWgklYTi98NUnRBg==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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==", + "license": "ISC" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "peer": true, + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/superagent": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.5", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.14.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supertest": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie-signature": "^1.2.2", + "methods": "^1.1.2", + "superagent": "^10.3.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tarn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", + "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/tildify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", + "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==", + "license": "MIT" + }, + "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, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/ts-node-dev/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/tsc-alias": { + "version": "1.8.17", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.17.tgz", + "integrity": "sha512-EIduCZHqbNwPm8BZYfq1aD7BQ697A4h6uSGMOFQfYGoQwfrYFTKwYfy9Bv42YxHkduVBcn9Zx0DkX111DKskyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + }, + "engines": { + "node": ">=16.20.2" + } + }, + "node_modules/tsc-alias/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "deprecated": "unmaintained", + "dev": true, + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", + "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vite/node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/vitest": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.6.tgz", + "integrity": "sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.6", + "@vitest/mocker": "3.2.6", + "@vitest/pretty-format": "^3.2.6", + "@vitest/runner": "3.2.6", + "@vitest/snapshot": "3.2.6", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.6", + "@vitest/ui": "3.2.6", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/api/package.json b/api/package.json new file mode 100644 index 0000000..3fa7bba --- /dev/null +++ b/api/package.json @@ -0,0 +1,74 @@ +{ + "name": "alphane-api", + "private": true, + "version": "0.0.1", + "description": "Alphane Backend", + "main": "src/server.js", + "scripts": { + "build": "tsc && tsc-alias", + "clean": "rm -rf ./dist", + "start": "ts-node-dev -r tsconfig-paths/register src/server.ts", + "start:scheduler": "ts-node-dev -r tsconfig-paths/register src/scheduler.ts", + "ts-node": "ts-node -r tsconfig-paths/register --logError", + "knex": "ts-node -r tsconfig-paths/register ./node_modules/.bin/knex --knexfile src/config.d/knexfile.ts", + "initializers": "ts-node -r tsconfig-paths/register --files src/initializers/index.ts", + "test": "vitest", + "lint": "eslint . --ext .js,.ts --ignore-path ../.gitignore", + "check-types": "tsc --noEmit" + }, + "author": "Caleb Burke", + "license": "ISC", + "dependencies": { + "@fontsource/audiowide": "^5.2.7", + "@sequelize/core": "^7.0.0-alpha.40", + "@sequelize/postgres": "^7.0.0-alpha.46", + "axios": "^1.7.3", + "cors": "^2.8.5", + "cron": "^4.4.0", + "dotenv": "^17.2.1", + "express": "^5.1.0", + "express-form-data": "^3.0.1", + "express-jwt": "^8.4.1", + "geist": "^1.7.0", + "helmet": "^8.1.0", + "jwks-rsa": "^3.1.0", + "knex": "^3.1.0", + "lodash": "^4.17.21", + "luxon": "^3.5.0", + "morgan": "^1.10.0", + "nodemailer": "^7.0.5", + "papaparse": "^5.4.1", + "redis": "^5.8.2", + "reflect-metadata": "^0.2.2", + "winston": "^3.17.0" + }, + "devDependencies": { + "@faker-js/faker": "^9.9.0", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.3", + "@types/express-form-data": "^2.0.5", + "@types/lodash": "^4.17.13", + "@types/luxon": "^3.4.2", + "@types/morgan": "^1.9.9", + "@types/node-schedule": "^2.1.5", + "@types/nodemailer": "^7.0.1", + "@types/papaparse": "^5.3.15", + "@types/sharp": "^0.31.1", + "@types/supertest": "^6.0.2", + "@typescript-eslint/eslint-plugin": "^8.40.0", + "@typescript-eslint/parser": "^8.40.0", + "axios-mock-adapter": "^2.1.0", + "eslint": "^8.57.1", + "eslint-config-prettier": "^10.1.8", + "fishery": "^2.2.2", + "prettier": "^3.1.1", + "supertest": "^7.1.4", + "ts-node": "^10.9.2", + "ts-node-dev": "^2.0.0", + "tsc-alias": "^1.8.8", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.6.3", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.2.4" + } +} \ No newline at end of file diff --git a/api/src/app.ts b/api/src/app.ts new file mode 100644 index 0000000..01db129 --- /dev/null +++ b/api/src/app.ts @@ -0,0 +1,60 @@ +import express, { type Request, type Response } from "express" +import cors from "cors" +import path from "path" +import helmet from "helmet" +import formData from "express-form-data" + +import { AUTH0_DOMAIN, FRONTEND_URL } from "@/config" +import { requestLoggerMiddleware } from "@/middlewares" +import router from "@/router" +import enhancedQsDecoder from "@/utils/enhanced-qs-decoder" + +export const app = express() +app.set("query parser", enhancedQsDecoder) + +app.use(express.json({ limit: "40mb" })) // for parsing application/json +app.use(express.urlencoded({ extended: true, limit: "40mb" })) // for parsing application/x-www-form-urlencoded +app.use(formData.parse({ autoClean: true, maxFieldsSize: 40 * 1024 * 1024 })) // for parsing multipart/form-data +app.use(formData.union()) // for parsing multipart/form-data +app.use( + helmet.contentSecurityPolicy({ + directives: { + "default-src": ["'self'", FRONTEND_URL, AUTH0_DOMAIN], + "base-uri": ["'self'"], + "block-all-mixed-content": [], + "font-src": ["'self'", "https:", "data:"], + "frame-ancestors": ["'self'"], + "img-src": ["'self'", "data:", "https:"], + "object-src": ["'none'"], + "script-src": ["'self'", "'unsafe-eval'"], + "script-src-attr": ["'none'"], + "style-src": ["'self'", "https:", "'unsafe-inline'"], + "worker-src": ["'self'", "blob:"], + "frame-src": ["'self'", "blob:", "data:", FRONTEND_URL, AUTH0_DOMAIN], + "connect-src": ["'self'", FRONTEND_URL, AUTH0_DOMAIN], + }, + }) +) + +// very basic CORS setup +app.use( + cors({ + origin: FRONTEND_URL, + optionsSuccessStatus: 200, + credentials: true, + }) +) + +app.use(requestLoggerMiddleware) + +app.use(router) + +// serves the static files generated by the front-end +app.use(express.static(path.join(__dirname, "web"))) + +// if no other routes match, just send the front-end +app.use((_req: Request, res: Response) => { + res.sendFile(path.join(__dirname, "web/index.html")) +}) + +export default app diff --git a/api/src/config.d/knexfile.ts b/api/src/config.d/knexfile.ts new file mode 100644 index 0000000..db8584b --- /dev/null +++ b/api/src/config.d/knexfile.ts @@ -0,0 +1,18 @@ +import { Knex } from "knex" + +import dbMigrationClient from "@/db/db-migration-client" + +// Hoists config from db client +const config: { [key: string]: Knex.Config } = { + development: { + ...dbMigrationClient.client.config, + }, + test: { + ...dbMigrationClient.client.config, + }, + production: { + ...dbMigrationClient.client.config, + }, +} + +export default config diff --git a/api/src/config.ts b/api/src/config.ts new file mode 100644 index 0000000..7ce7c60 --- /dev/null +++ b/api/src/config.ts @@ -0,0 +1,68 @@ +import path from "path" +import * as dotenv from "dotenv" + +import { stripTrailingSlash } from "@/utils/strip-trailing-slash" + +export const NODE_ENV = process.env.NODE_ENV || "development" + +let dotEnvPath +switch (process.env.NODE_ENV) { + case "test": + dotEnvPath = path.resolve(__dirname, "../.env.test") + break + case "production": + dotEnvPath = path.resolve(__dirname, "../.env.production") + break + default: + dotEnvPath = path.resolve(__dirname, "../.env.development") +} + +dotenv.config({ path: dotEnvPath }) + +if (process.env.NODE_ENV !== "test") { + console.log("Loading env: ", dotEnvPath) +} + +export const API_PORT = process.env.API_PORT || "3000" +export const JOB_PORT = process.env.JOB_PORT || "3001" + +export const FRONTEND_URL = process.env.FRONTEND_URL || "" +export const AUTH0_DOMAIN = stripTrailingSlash(process.env.VITE_AUTH0_DOMAIN || "") +export const AUTH0_AUDIENCE = process.env.VITE_AUTH0_AUDIENCE +export const AUTH0_REDIRECT = process.env.VITE_AUTH0_REDIRECT || process.env.FRONTEND_URL || "" + +export const APPLICATION_NAME = process.env.VITE_APPLICATION_NAME || "" + +export const DB_HOST = process.env.DB_HOST || "" +export const DB_USERNAME = process.env.DB_USERNAME || "" +export const DB_PASSWORD = process.env.DB_PASSWORD || "" +export const DB_DATABASE = process.env.DB_DATABASE || "" +export const DB_PORT = parseInt(process.env.DB_PORT || "1433") +export const DB_TRUST_SERVER_CERTIFICATE = process.env.DB_TRUST_SERVER_CERTIFICATE === "true" + +export const REDIS_CONNECTION_URL = process.env.REDIS_CONNECTION_URL || "" + +export const DB_HEALTH_CHECK_INTERVAL_SECONDS = parseInt( + process.env.DB_HEALTH_CHECK_INTERVAL_SECONDS || "5" +) +export const DB_HEALTH_CHECK_TIMEOUT_SECONDS = parseInt( + process.env.DB_HEALTH_CHECK_TIMEOUT_SECONDS || "10" +) +export const DB_HEALTH_CHECK_RETRIES = parseInt(process.env.DB_HEALTH_CHECK_RETRIES || "3") +export const DB_HEALTH_CHECK_START_PERIOD_SECONDS = parseInt( + process.env.DB_HEALTH_CHECK_START_PERIOD_SECONDS || "5" +) + +export const SEQUELIZE_LOGGING = process.env.SEQUELIZE_LOGGING === "true" + +export const RELEASE_TAG = process.env.RELEASE_TAG || "" +export const GIT_COMMIT_HASH = process.env.GIT_COMMIT_HASH || "" + +export const RUN_SCHEDULER = process.env.RUN_SCHEDULER || "false" + +export const DEFAULT_LOG_LEVEL = process.env.DEFAULT_LOG_LEVEL || "debug" + +// Internal Helpers +export const APP_ROOT_PATH = path.resolve(__dirname, "..") +export const SOURCE_ROOT_PATH = + NODE_ENV === "production" ? path.join(APP_ROOT_PATH, "dist") : path.join(APP_ROOT_PATH, "src") diff --git a/api/src/controllers/README.md b/api/src/controllers/README.md new file mode 100644 index 0000000..b2c7fc0 --- /dev/null +++ b/api/src/controllers/README.md @@ -0,0 +1,99 @@ +# Controllers + +These files map api routes to models, policies, services, and serializers. +See https://guides.rubyonrails.org/routing.html#crud-verbs-and-actions + +e.g. + +```typescript +router.route("/api/users").post(UsersController.create) +``` + +maps the `/api/users` POST endpoint to the `UsersController#create` instance method. + +Controllers are advantageous because they provide a suite of helper methods to access various request methods. .e.g. `currentUser`, or `params`. They also provide a location to perform policy checks. + +Controllers should implement the BaseController, and provide instance methods. +The `BaseController` provides the magic that lets those methods map to an appropriate route. + +## Namespacing + +If you need an action that syncs a user with an external directory, a POST route `/api/users/:userId/directory-sync` is the best way to avoid future conflicts and refactors. To implement this you need to "namespace/modularize" the controller. Generally speaking, it is more flexible to keep all routes as CRUD actions, and nest controllers as needed, than it is to add custom routes to a given controller. + +e.g. `Users.DirectorySyncController.create` is preferred to `UsersController#directorySync`. Once you start using non-CRUD actions, your controllers will quickly expand beyond human readability and comprehension. Opting to use PascalCase for namespaces as that is the best way to avoid conflicts with local variables. + +This is how you would create a namespaced controller: + +```bash +api/ +|-- src/ +| |-- controllers/ +| |-- users/ +| |-- directory-sync-controller.ts +| |-- index.ts +| |-- users-controller.ts +``` + +```typescript +// api/src/controllers/users/users-controller.ts +import { User } from "@/models" +import BaseController from "@/base-controller" + +export class UsersController extends BaseController { + static async create() { + // Perform user creation + // Perform policy check + // Call appropriate service + // Perform serialization if needed + // Return response + } +} +``` + +```typescript +// api/src/controllers/users/directory-sync-controller.ts +import { User } from "@/models" +import BaseController from "@/base-controller" + +export class DirectorySyncController extends BaseController { + static async create() { + // Perform user lookup + // Perform policy check + // Call appropriate service + // Perform serialization if needed + // Return response + } +} +``` + +```typescript +// api/src/controllers/users/index.ts +export { DirectorySyncController } from "./directory-sync-controller" +``` + +```typescript +// api/src/controllers/index.ts +import * as Users from "./users" + +import { UsersController } from "./users-controller" + +export { Users, UsersController } +``` + +```typescript +// api/src/routes.ts +import { Router } from "express" + +import { Users } from "@/controllers" + +const router = Router() + +router.route("/api/users").get(UsersController.index).post(UsersController.create) +router + .route("/api/users/:userId") + .get(UsersController.show) + .put(UsersController.update) + .delete(UsersController.destroy) + +router.route("/api/users/:userId/directory-sync").post(Users.DirectorySyncController.create) +``` diff --git a/api/src/controllers/base-controller.ts b/api/src/controllers/base-controller.ts new file mode 100644 index 0000000..c16a9bf --- /dev/null +++ b/api/src/controllers/base-controller.ts @@ -0,0 +1,196 @@ +import { NextFunction, Request, Response } from "express" +import { Attributes, Model, Order, WhereOptions } from "@sequelize/core" +import { dropRight, isEmpty, isNil, uniqBy } from "lodash" + +import User from "@/models/user" +import { type BaseScopeOptions } from "@/policies" + +export type Actions = "index" | "show" | "new" | "edit" | "create" | "update" | "destroy" + +type ControllerRequest = Request & { + currentUser: User +} + +/** Keep in sync with web/src/api/base-api.ts */ +export type ModelOrder = Order & + ( + | [string, string] + | [string, string, string] + | [string, string, string, string] + | [string, string, string, string, string] + | [string, string, string, string, string, string] + ) + +// Keep in sync with web/src/api/base-api.ts +const MAX_PER_PAGE = 1000 +const MAX_PER_PAGE_EQUIVALENT = -1 +const DEFAULT_PER_PAGE = 10 + +// See https://guides.rubyonrails.org/routing.html#crud-verbs-and-actions +export class BaseController { + protected request: ControllerRequest + protected response: Response + protected next: NextFunction + + cacheIndex = false + cacheShow = false + cacheDuration = 0 + cachePrefix = "" + cacheDependents = new Array() + + constructor(req: Request, res: Response, next: NextFunction) { + // Assumes authorization has occured first in + // api/src/middlewares/jwt-middleware.ts and api/src/middlewares/authorization-middleware.ts + // At some future point it would make sense to do all that logic as + // controller actions to avoid the need for hack + this.request = req as ControllerRequest + this.response = res as Response + this.next = next + } + + static get index() { + return async (req: Request, res: Response, next: NextFunction) => { + const controllerInstance = new this(req, res, next) + return controllerInstance.index().catch(next) + } + } + + // Usage app.post("/api/users", UsersController.create) + // maps /api/users to UsersController#create() + static get create() { + return async (req: Request, res: Response, next: NextFunction) => { + const controllerInstance = new this(req, res, next) + return controllerInstance.create().catch(next) + } + } + + static get show() { + return async (req: Request, res: Response, next: NextFunction) => { + const controllerInstance = new this(req, res, next) + return controllerInstance.show().catch(next) + } + } + + static get update() { + return async (req: Request, res: Response, next: NextFunction) => { + const controllerInstance = new this(req, res, next) + return controllerInstance.update().catch(next) + } + } + + static get destroy() { + return async (req: Request, res: Response, next: NextFunction) => { + const controllerInstance = new this(req, res, next) + return controllerInstance.destroy().catch(next) + } + } + + index(): Promise { + throw new Error("Not Implemented") + } + + create(): Promise { + throw new Error("Not Implemented") + } + + show(): Promise { + throw new Error("Not Implemented") + } + + update(): Promise { + throw new Error("Not Implemented") + } + + destroy(): Promise { + throw new Error("Not Implemented") + } + + // Internal helpers + + // This should have been loaded in the authorization middleware + // Currently assuming that authorization happens before this controller gets called. + // Child controllers that are on an non-authorizable route should override this method + // and return undefined + get currentUser(): User { + return this.request.currentUser + } + + get params() { + return this.request.params + } + + get query() { + return this.request.query + } + + get pagination() { + const page = parseInt(this.query.page?.toString() || "") || 1 + const perPage = parseInt(this.query.perPage?.toString() || "") || DEFAULT_PER_PAGE + const limit = this.determineLimit(perPage) + const offset = (page - 1) * limit + return { + page, + perPage, + limit, + offset, + } + } + + buildWhere( + overridableOptions: WhereOptions> = {}, + nonOverridableOptions: WhereOptions> = {} + ): WhereOptions> { + // TODO: consider if we should add parsing of Sequelize [Op.is] and [Op.not] here + // or in the api/src/utils/enhanced-qs-decoder.ts function + const queryWhere = this.query.where as WhereOptions> + return { + ...overridableOptions, + ...queryWhere, + ...nonOverridableOptions, + } as WhereOptions> + } + + buildFilterScopes>( + initialScopes: BaseScopeOptions[] = [], + filtersOverride?: FilterOptions + ): BaseScopeOptions[] { + const filters = filtersOverride ?? (this.query.filters as FilterOptions) + const scopes = initialScopes + if (!isEmpty(filters)) { + Object.entries(filters).forEach(([key, value]) => { + scopes.push({ method: [key, value] }) + }) + } + + return scopes + } + + buildOrder( + overridableOrder: ModelOrder[] = [], + nonOverridableOrder: ModelOrder[] = [] + ): ModelOrder[] | undefined { + const orderQuery = this.query.order as unknown as ModelOrder[] | undefined + + if (isNil(orderQuery)) { + return [...nonOverridableOrder, ...overridableOrder] + } + + const order = [...nonOverridableOrder, ...orderQuery, ...overridableOrder] + const uniqueOrder = uniqBy(order, (order) => { + const orderExcludingDirection = dropRight(order) + return orderExcludingDirection.join(".").toLowerCase() + }) + + return uniqueOrder + } + + private determineLimit(perPage: number) { + if (perPage === MAX_PER_PAGE_EQUIVALENT) { + return MAX_PER_PAGE + } + + return Math.max(1, Math.min(perPage, MAX_PER_PAGE)) + } +} + +export default BaseController diff --git a/api/src/controllers/current-user-controller.ts b/api/src/controllers/current-user-controller.ts new file mode 100644 index 0000000..a9411fc --- /dev/null +++ b/api/src/controllers/current-user-controller.ts @@ -0,0 +1,31 @@ +import { User } from "@/models" +import { UsersPolicy } from "@/policies" +import { ShowSerializer } from "@/serializers/current-user" +import BaseController from "@/controllers/base-controller" + +export class CurrentUserController extends BaseController { + async show() { + try { + const user = this.currentUser + const policy = this.buildPolicy(user) + if (!policy.show()) { + return this.response.status(403).json({ + message: "You are not authorized to view the current user", + }) + } + + const serializedUser = ShowSerializer.perform(user) + return this.response.json({ user: serializedUser, policy }) + } catch (error) { + return this.response.status(400).json({ + message: `Error fetching current user: ${error}`, + }) + } + } + + private buildPolicy(user: User) { + return new UsersPolicy(this.currentUser, user) + } +} + +export default CurrentUserController diff --git a/api/src/controllers/index.ts b/api/src/controllers/index.ts new file mode 100644 index 0000000..f88538f --- /dev/null +++ b/api/src/controllers/index.ts @@ -0,0 +1,3 @@ +// Controllers +export { CurrentUserController } from "./current-user-controller" +export { UsersController } from "./users-controller" diff --git a/api/src/controllers/users-controller.ts b/api/src/controllers/users-controller.ts new file mode 100644 index 0000000..89fbd78 --- /dev/null +++ b/api/src/controllers/users-controller.ts @@ -0,0 +1,146 @@ +import { isNil } from "lodash" + +import logger from "@/utils/logger" +import { User } from "@/models" +import { UsersPolicy } from "@/policies" +import { CreateService, DestroyService, UpdateService } from "@/services/users" +import { IndexSerializer, ShowSerializer } from "@/serializers/users" +import BaseController from "@/controllers/base-controller" + +export class UsersController extends BaseController { + async index() { + try { + const where = this.buildWhere() + const scopes = this.buildFilterScopes() + const scopedUsers = UsersPolicy.applyScope(scopes, this.currentUser) + + const totalCount = await scopedUsers.count({ where }) + const users = await scopedUsers.findAll({ + where, + limit: this.pagination.limit, + offset: this.pagination.offset, + order: this.buildOrder(), + }) + const serializedUsers = IndexSerializer.perform(users) + return this.response.json({ + users: serializedUsers, + totalCount, + }) + } catch (error) { + logger.error("Error fetching users" + error) + return this.response.status(400).json({ + message: `Error fetching users: ${error}`, + }) + } + } + + async show() { + try { + const user = await this.loadUser() + if (isNil(user)) { + return this.response.status(404).json({ + message: "User not found", + }) + } + + const policy = this.buildPolicy(user) + if (!policy.show()) { + return this.response.status(403).json({ + message: "You are not authorized to view this user", + }) + } + const serializedUser = ShowSerializer.perform(user) + return this.response.json({ user: serializedUser, policy }) + } catch (error) { + logger.error("Error fetching user" + error) + return this.response.status(400).json({ + message: `Error fetching user: ${error}`, + }) + } + } + + async create() { + try { + const policy = this.buildPolicy() + if (!policy.create()) { + return this.response.status(403).json({ + message: "You are not authorized to create users", + }) + } + + const permittedAttributes = policy.permitAttributesForCreate(this.request.body) + const user = await CreateService.perform(permittedAttributes) + const serializedUser = ShowSerializer.perform(user) + return this.response.status(201).json({ user: serializedUser }) + } catch (error) { + logger.error("Error creating user" + error) + return this.response.status(422).json({ + message: `Error creating user: ${error}`, + }) + } + } + + async update() { + try { + const user = await this.loadUser() + if (isNil(user)) { + return this.response.status(404).json({ + message: "User not found", + }) + } + + const policy = this.buildPolicy(user) + if (!policy.update()) { + return this.response.status(403).json({ + message: "You are not authorized to update this user", + }) + } + + const permittedAttributes = policy.permitAttributes(this.request.body) + const updatedUser = await UpdateService.perform(user, permittedAttributes) + const serializedUser = ShowSerializer.perform(updatedUser) + return this.response.json({ user: serializedUser }) + } catch (error) { + logger.error("Error updating user" + error) + return this.response.status(422).json({ + message: `Error updating user: ${error}`, + }) + } + } + + async destroy() { + try { + const user = await this.loadUser() + if (isNil(user)) { + return this.response.status(404).json({ + message: "User not found", + }) + } + + const policy = this.buildPolicy(user) + if (!policy.destroy()) { + return this.response.status(403).json({ + message: "You are not authorized to delete this user", + }) + } + + await DestroyService.perform(user) + return this.response.status(204).send() + } catch (error) { + logger.error("Error deleting user" + error) + return this.response.status(422).json({ + message: `Error deleting user: ${error}`, + }) + } + } + + private async loadUser() { + return User.findByPk(this.params.userId) + } + + private buildPolicy(user: User = User.build()) { + return new UsersPolicy(this.currentUser, user) + } +} + +export default UsersController diff --git a/api/src/db/cache-client.ts b/api/src/db/cache-client.ts new file mode 100644 index 0000000..0c6498a --- /dev/null +++ b/api/src/db/cache-client.ts @@ -0,0 +1,94 @@ +import { REDIS_CONNECTION_URL } from "@/config" +import { logger } from "@/utils/logger" +import { RedisClientType, createClient } from "@redis/client" +import { ScanOptions } from "@redis/client/dist/lib/commands/SCAN" + +class CacheClient { + protected client: RedisClientType + protected failures: number = 0 + + constructor() { + logger.info(`INIT CACHE: ${REDIS_CONNECTION_URL}`) + this.client = createClient({ url: REDIS_CONNECTION_URL }) + + this.client.on("error", (err) => { + this.onError(err) + }) + this.client.on("connect", async () => { + this.failures = 0 + await this.setValueNoExpire("TESTING", 123) + logger.info("Redis Client Connect") + }) + } + + // eslint-disable-next-line + onError(err: any) { + if (this.failures < 5) { + this.failures++ + logger.error(`Redis Connection Error ${this.failures}: ${err.message}`) + } else if (this.failures == 5) { + this.failures++ + logger.error("Giving up on Redis") + } else { + logger.error(`Redis Generic Error: ${err.message}`) + } + } + + async getClient() { + if (!this.client.isOpen) await this.client.connect() + return this + } + + // eslint-disable-next-line + async setValue(key: string, value: any, expireSeconds = 90) { + if (expireSeconds <= 0) this.setValueNoExpire(key, value) + else this.client.set(key, value, { EX: expireSeconds }) + } + // eslint-disable-next-line + async setValueNoExpire(key: string, value: any) { + this.client.set(key, value) + } + async getValue(key: string) { + return this.client.get(key) + } + async deleteValue(key: string) { + return this.client.del(key) + } + + async deleteValuesByPattern(pattern: string) { + const scanCommand = { MATCH: `${pattern}*` } as ScanOptions + let cursor = "0" + + do { + const reply = await this.client.scan(cursor, scanCommand) + cursor = reply.cursor + const keys = reply.keys + + if (keys.length > 0) { + await this.client.del(keys) + } + } while (cursor !== "0") + } + + async getKeysByPattern(pattern: string): Promise { + const scanCommand = { MATCH: `${pattern}*` } as ScanOptions + let cursor = "0" + let matches = new Array() + + do { + const reply = await this.client.scan(cursor, scanCommand) + cursor = reply.cursor + const keys = reply.keys + + if (keys.length > 0) { + matches = matches.concat(keys) + } + } while (cursor !== "0") + + return matches + } +} + +const cache = new CacheClient() + +export default cache diff --git a/api/src/db/data/README.md b/api/src/db/data/README.md new file mode 100644 index 0000000..26c1fc1 --- /dev/null +++ b/api/src/db/data/README.md @@ -0,0 +1,4 @@ +# DB Data + +This folder contains SQL scripts or JSON files that contain data for seeding the database. +Most of these things will be converted into seeds and factories, but they live here in the meantime. diff --git a/api/src/db/db-client.ts b/api/src/db/db-client.ts new file mode 100644 index 0000000..283e5cc --- /dev/null +++ b/api/src/db/db-client.ts @@ -0,0 +1,53 @@ +import { Sequelize, Options } from "@sequelize/core" +import { PostgresDialect } from "@sequelize/postgres" +import { isEmpty, isNil } from "lodash" + +import { + DB_DATABASE, + DB_HOST, + DB_PASSWORD, + DB_PORT, + DB_USERNAME, + NODE_ENV, + SEQUELIZE_LOGGING, +} from "@/config" +import compactSql from "@/utils/compact-sql" + +if (isEmpty(DB_DATABASE)) throw new Error("database name is unset.") +if (isEmpty(DB_USERNAME)) throw new Error("database username is unset.") +if (isEmpty(DB_PASSWORD)) throw new Error("database password is unset.") +if (isEmpty(DB_HOST)) throw new Error("database host is unset.") +if (isNil(DB_PORT) || isNaN(DB_PORT)) throw new Error("database port is unset.") + +function sqlLogger(query: string) { + console.log(compactSql(query)) +} + +// See https://sequelize.org/docs/v7/databases/postgres/ +export const SEQUELIZE_CONFIG: Options = { + dialect: PostgresDialect, + database: DB_DATABASE, + user: DB_USERNAME, + password: DB_PASSWORD, + host: DB_HOST, + port: DB_PORT, + ssl: NODE_ENV !== "production" ? false : { rejectUnauthorized: false }, + schema: "public", // default - explicit for clarity + logging: SEQUELIZE_LOGGING ? sqlLogger : false, + pool: { + max: 20, + min: 2, + acquire: 60_000, + idle: 10_000, + evict: 10_000, + }, + define: { + underscored: true, + timestamps: true, // default - explicit for clarity. + paranoid: true, // adds deleted_at column + }, +} + +const db = new Sequelize(SEQUELIZE_CONFIG) + +export default db diff --git a/api/src/db/db-migration-client.ts b/api/src/db/db-migration-client.ts new file mode 100644 index 0000000..e58c15a --- /dev/null +++ b/api/src/db/db-migration-client.ts @@ -0,0 +1,54 @@ +import path from "path" + +import knex, { Knex } from "knex" +import { isEmpty, isNil, merge } from "lodash" + +import { DB_DATABASE, DB_HOST, DB_PASSWORD, DB_PORT, DB_USERNAME, NODE_ENV } from "@/config" + +if (isEmpty(DB_DATABASE)) throw new Error("database name is unset.") +if (isEmpty(DB_USERNAME)) throw new Error("database username is unset.") +if (isEmpty(DB_PASSWORD)) throw new Error("database password is unset.") +if (isEmpty(DB_HOST)) throw new Error("database host is unset.") +if (isNil(DB_PORT) || isNaN(DB_PORT)) throw new Error("database port is unset.") + +export function buildKnexConfig(options?: Knex.Config): Knex.Config { + return merge( + { + client: "pg", + connection: { + host: DB_HOST, + user: DB_USERNAME, + password: DB_PASSWORD, + database: DB_DATABASE, + port: DB_PORT, + ssl: + NODE_ENV !== "production" + ? false + : { + require: true, // Enforce SSL + rejectUnauthorized: false, // Disable certificate verification (common for Azure) + }, + /* options: { + encrypt: true, + trustServerCertificate: DB_TRUST_SERVER_CERTIFICATE, + }, */ + }, + migrations: { + directory: path.resolve(__dirname, "./migrations"), + extension: "ts", + stub: path.resolve(__dirname, "./templates/sample-migration.ts"), + }, + seeds: { + directory: path.resolve(__dirname, `./seeds/${NODE_ENV}`), + extension: "ts", + stub: path.resolve(__dirname, "./templates/sample-seed.ts"), + }, + }, + options + ) +} + +const config = buildKnexConfig() +const dbMigrationClient = knex(config) + +export default dbMigrationClient diff --git a/api/src/db/migrations/20260619204725_create-users-table.ts b/api/src/db/migrations/20260619204725_create-users-table.ts new file mode 100644 index 0000000..6f4f44f --- /dev/null +++ b/api/src/db/migrations/20260619204725_create-users-table.ts @@ -0,0 +1,36 @@ +import type { Knex } from "knex" + +export async function up(knex: Knex): Promise { + await knex.schema.createTable("users", function (table) { + table.increments("id").notNullable().primary() + table.string("email", 100).notNullable() + table.string("auth0_subject", 100).notNullable() + table.string("first_name", 100).notNullable() + table.string("last_name", 100).notNullable() + table.string("display_name", 200).notNullable() + table.string("roles", 255).notNullable() + + table + .specificType("created_at", "TIMESTAMP WITH TIME ZONE") + .notNullable() + .defaultTo(knex.raw("CURRENT_TIMESTAMP(0)")) + table + .specificType("updated_at", "TIMESTAMP WITH TIME ZONE") + .notNullable() + .defaultTo(knex.raw("CURRENT_TIMESTAMP(0)")) + table.specificType("deleted_at", "TIMESTAMP WITH TIME ZONE") + + table.unique(["email"], { + indexName: "users_email_unique", + predicate: knex.whereNull("deleted_at"), + }) + table.unique(["auth0_subject"], { + indexName: "users_auth0_subject_unique", + predicate: knex.whereNull("deleted_at"), + }) + }) +} + +export async function down(knex: Knex): Promise { + await knex.schema.dropTable("users") +} diff --git a/api/src/db/migrator.ts b/api/src/db/migrator.ts new file mode 100644 index 0000000..3fa44cd --- /dev/null +++ b/api/src/db/migrator.ts @@ -0,0 +1,74 @@ +import express, { Request, Response } from "express" +import { join } from "path" + +import { NODE_ENV } from "@/config" +import dbMigrationClient from "@/db/db-migration-client" +import { logger } from "@/utils/logger" + +export class Migrator { + readonly migrationRouter + + constructor() { + this.migrationRouter = express.Router() + + this.migrationRouter.get("/", async (_req: Request, res: Response) => { + return res.json({ data: await this.listMigrations() }) + }) + + this.migrationRouter.get("/up", async (_req: Request, res: Response) => { + try { + await this.migrateUp() + } catch (err) { + logger.error(err) + } + return res.json({ data: await migrator.listMigrations() }) + }) + + this.migrationRouter.get("/down", async (_req: Request, res: Response) => { + try { + await this.migrateDown() + } catch (err) { + logger.error(err) + } + return res.json({ data: await this.listMigrations() }) + }) + + this.migrationRouter.get("/seed/:environment", async (req: Request, res: Response) => { + try { + await this.seedUp(req.params.environment) + } catch (err) { + logger.error(err) + } + return res.json({ data: "Seeding" }) + }) + } + + listMigrations() { + return dbMigrationClient.migrate.list({ directory: join(__dirname, "migrations") }) + } + + async migrateUp() { + logger.warn("-------- MIGRATE UP ---------") + return dbMigrationClient.migrate.up({ directory: join(__dirname, "migrations") }) + } + + async migrateDown() { + logger.warn("-------- MIGRATE DOWN ---------") + return dbMigrationClient.migrate.down({ directory: join(__dirname, "migrations") }) + } + + async migrateLatest() { + logger.warn("-------- MIGRATE LATEST ---------") + return dbMigrationClient.migrate.latest({ directory: join(__dirname, "migrations") }) + } + + async seedUp(environment?: string) { + logger.warn("-------- SEED UP ---------") + return dbMigrationClient.seed.run({ + directory: join(__dirname, "seeds", environment || NODE_ENV), + }) + } +} +const migrator = new Migrator() + +export default migrator diff --git a/api/src/db/seeds/development/.gitkeep b/api/src/db/seeds/development/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/api/src/db/seeds/development/000.seed-system-user.ts b/api/src/db/seeds/development/000.seed-system-user.ts new file mode 100644 index 0000000..2d4b8bb --- /dev/null +++ b/api/src/db/seeds/development/000.seed-system-user.ts @@ -0,0 +1,31 @@ +import { CreationAttributes } from "@sequelize/core" +import { isNil } from "lodash" + +import logger from "@/utils/logger" +import { CreateService } from "@/services/users" +import { User } from "@/models" + +export async function seed(): Promise { + const systemUserAttributes: CreationAttributes = { + email: "system.user@alphane.com", + auth0Subject: "NO_LOGIN_system.user@alphane.com", + firstName: "System", + lastName: "User", + displayName: "System User", + roles: [User.Roles.SYSTEM_ADMIN], + } + + const user = await User.findOne({ + where: { + email: systemUserAttributes.email, + }, + }) + + if (isNil(user)) { + const createdUser = await CreateService.perform(systemUserAttributes) + logger.debug("System User created:", createdUser.dataValues) + } else { + await user.update(systemUserAttributes) + logger.debug("System User updated:", user.dataValues) + } +} diff --git a/api/src/db/seeds/production/.gitkeep b/api/src/db/seeds/production/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/api/src/db/seeds/production/000.seed-system-user.ts b/api/src/db/seeds/production/000.seed-system-user.ts new file mode 100644 index 0000000..2d4b8bb --- /dev/null +++ b/api/src/db/seeds/production/000.seed-system-user.ts @@ -0,0 +1,31 @@ +import { CreationAttributes } from "@sequelize/core" +import { isNil } from "lodash" + +import logger from "@/utils/logger" +import { CreateService } from "@/services/users" +import { User } from "@/models" + +export async function seed(): Promise { + const systemUserAttributes: CreationAttributes = { + email: "system.user@alphane.com", + auth0Subject: "NO_LOGIN_system.user@alphane.com", + firstName: "System", + lastName: "User", + displayName: "System User", + roles: [User.Roles.SYSTEM_ADMIN], + } + + const user = await User.findOne({ + where: { + email: systemUserAttributes.email, + }, + }) + + if (isNil(user)) { + const createdUser = await CreateService.perform(systemUserAttributes) + logger.debug("System User created:", createdUser.dataValues) + } else { + await user.update(systemUserAttributes) + logger.debug("System User updated:", user.dataValues) + } +} diff --git a/api/src/db/seeds/test/.gitkeep b/api/src/db/seeds/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/api/src/db/seeds/test/000.seed-system-user.ts b/api/src/db/seeds/test/000.seed-system-user.ts new file mode 100644 index 0000000..2d4b8bb --- /dev/null +++ b/api/src/db/seeds/test/000.seed-system-user.ts @@ -0,0 +1,31 @@ +import { CreationAttributes } from "@sequelize/core" +import { isNil } from "lodash" + +import logger from "@/utils/logger" +import { CreateService } from "@/services/users" +import { User } from "@/models" + +export async function seed(): Promise { + const systemUserAttributes: CreationAttributes = { + email: "system.user@alphane.com", + auth0Subject: "NO_LOGIN_system.user@alphane.com", + firstName: "System", + lastName: "User", + displayName: "System User", + roles: [User.Roles.SYSTEM_ADMIN], + } + + const user = await User.findOne({ + where: { + email: systemUserAttributes.email, + }, + }) + + if (isNil(user)) { + const createdUser = await CreateService.perform(systemUserAttributes) + logger.debug("System User created:", createdUser.dataValues) + } else { + await user.update(systemUserAttributes) + logger.debug("System User updated:", user.dataValues) + } +} diff --git a/api/src/db/templates/sample-migration.ts b/api/src/db/templates/sample-migration.ts new file mode 100644 index 0000000..8f39bb7 --- /dev/null +++ b/api/src/db/templates/sample-migration.ts @@ -0,0 +1,9 @@ +import type { Knex } from "knex" + +export async function up(knex: Knex): Promise { + throw new Error("Not implemented") +} + +export async function down(knex: Knex): Promise { + throw new Error("Not implemented") +} diff --git a/api/src/db/templates/sample-seed.ts b/api/src/db/templates/sample-seed.ts new file mode 100644 index 0000000..10335cf --- /dev/null +++ b/api/src/db/templates/sample-seed.ts @@ -0,0 +1,33 @@ +import { Knex } from "knex" +import { isNil } from "lodash" + +import logger from "@/utils/logger" +import { User } from "@/models" + +export async function seed(_knex: Knex): Promise { + const usersAttributes = [ + { + email: "system.user@richter-guardian.com", + auth0Subject: "system.user@richter-guardian.com", + firstName: "System", + lastName: "User", + displayName: "System User", + roles: [User.Roles.SYSTEM_ADMIN], + title: "System User", + }, + ] + for (const attributes of usersAttributes) { + let user = await User.findOne({ + where: { + email: attributes.email, + }, + }) + if (isNil(user)) { + user = await User.create(attributes) + logger.debug("User created:", user.dataValues) + } else { + await user.update(attributes) + logger.debug("User updated:", user.dataValues) + } + } +} diff --git a/api/src/db/test-connection.ts b/api/src/db/test-connection.ts new file mode 100644 index 0000000..94d7c51 --- /dev/null +++ b/api/src/db/test-connection.ts @@ -0,0 +1,17 @@ +import dbMigrationClient from "@/db/db-migration-client" +import { logger } from "@/utils/logger" + +export async function isValidConnection() { + try { + await dbMigrationClient.raw("SELECT GETDATE()") + logger.info("Connection has been established successfully.") + return true + } catch (error) { + logger.error("Unable to connect to the database: " + error) + return false + } +} + +if (require.main === module) { + isValidConnection() +} diff --git a/api/src/db/utils/knex-query-to-sequelize-raw.ts b/api/src/db/utils/knex-query-to-sequelize-raw.ts new file mode 100644 index 0000000..def8c7b --- /dev/null +++ b/api/src/db/utils/knex-query-to-sequelize-raw.ts @@ -0,0 +1,54 @@ +import { Knex } from "knex" +import { QueryTypes, QueryOptionsWithType } from "@sequelize/core" + +import db from "@/db/db-client" + +type QueryOptions = Omit, "bind" | "type"> + +// TODO: fix types to show that it might return null +export async function knexQueryToSequelizeSelect( + knexQuery: Knex.QueryBuilder, + options: QueryOptions = {} +) { + const { sql: knexSql, bindings } = knexQuery.toSQL().toNative() + const { sql: sequelizeSql, bind } = knexSqlNativeToSequelizeQueryWithBind({ + sql: knexSql, + bindings, + }) + return db.query(sequelizeSql, { + ...options, + bind, + type: QueryTypes.SELECT, + }) +} + +/** + * Note siganture is chosen so you can pass knexQuery.toSQL().toNative() directly + * + * Currently only tested with MSSQL dialect + * + * @param sqlWithKnexBindings knexQuery.toSQL().toNative().sql + * @param bindings knexQuery.toSQL().toNative().bindings + * @returns { sql: string, bind: unknown[] } in Sequelize format + */ +export function knexSqlNativeToSequelizeQueryWithBind({ + sql: sqlWithKnexBindings, + bindings, +}: { + sql: string + bindings: readonly unknown[] +}): { sql: string; bind: unknown[] } { + let sqlWithSequelizeBindings = sqlWithKnexBindings + // converts "@p0" to "$1", "@p1" to "$2", etc. + bindings.forEach((_, i) => { + const pattern = new RegExp(`@p${i}\\b`, "g") + sqlWithSequelizeBindings = sqlWithSequelizeBindings.replace(pattern, `$${i + 1}`) + }) + + const mutableBindings = [...bindings] + + return { + sql: sqlWithSequelizeBindings, + bind: mutableBindings, + } +} diff --git a/api/src/db/utils/safe-parse-json.ts b/api/src/db/utils/safe-parse-json.ts new file mode 100644 index 0000000..41611f3 --- /dev/null +++ b/api/src/db/utils/safe-parse-json.ts @@ -0,0 +1,18 @@ +import { logger } from "@/utils/logger" + +export function safeJsonParse(values: string): any[] { + try { + const lines = JSON.parse(values) + if (Array.isArray(lines)) { + return lines + } else { + logger.error("Parsed value is not an array.") + return [] + } + } catch (error) { + logger.error(`Error parsing JSON: ${error}`, { error }) + return [] + } +} + +export default safeJsonParse diff --git a/api/src/initializers/05-wait-for-database.ts b/api/src/initializers/05-wait-for-database.ts new file mode 100644 index 0000000..547e588 --- /dev/null +++ b/api/src/initializers/05-wait-for-database.ts @@ -0,0 +1,103 @@ +import knex, { type Knex } from "knex" + +import { + DB_HEALTH_CHECK_INTERVAL_SECONDS, + DB_HEALTH_CHECK_RETRIES, + DB_HEALTH_CHECK_START_PERIOD_SECONDS, + DB_HEALTH_CHECK_TIMEOUT_SECONDS, +} from "@/config" +import logger from "@/utils/logger" +import sleep from "@/utils/sleep" +import { + isCredentialFailure, + isNetworkFailure, + isSocketFailure, + isMissingDatabaseFailure, +} from "@/utils/db-error-helpers" +import { buildKnexConfig } from "@/db/db-migration-client" + +function checkHealth(dbMigrationClient: Knex, timeoutSeconds: number) { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error("Connection timeout")), timeoutSeconds * 1000) + dbMigrationClient + .raw("SELECT 1") + .then(() => { + clearTimeout(timer) + resolve(null) + }) + .catch(reject) + }) +} + +export async function waitForDatabase({ + intervalSeconds = DB_HEALTH_CHECK_INTERVAL_SECONDS, + timeoutSeconds = DB_HEALTH_CHECK_TIMEOUT_SECONDS, + retries = DB_HEALTH_CHECK_RETRIES, + startPeriodSeconds = DB_HEALTH_CHECK_START_PERIOD_SECONDS, +}: { + intervalSeconds?: number + timeoutSeconds?: number + retries?: number + startPeriodSeconds?: number +} = {}): Promise { + await sleep(startPeriodSeconds) + + logger.info("Attempting direct to database connection...") + const databaseConfig = buildKnexConfig() + + let dbMigrationClient = knex(databaseConfig) + let isDatabaseSocketReady = false + + for (let i = 0; i < retries; i++) { + try { + await checkHealth(dbMigrationClient, timeoutSeconds) + logger.info("Database connection successful.") + return + } catch (error) { + if (isSocketFailure(error)) { + logger.info(`Database socket is not ready, retrying... ${error}`, { error }) + await sleep(intervalSeconds) + } else if (isNetworkFailure(error)) { + logger.info(`Network error, retrying... ${error}`, { error }) + await sleep(intervalSeconds) + } else if (isCredentialFailure(error)) { + if (isDatabaseSocketReady) { + logger.error(`Database connection failed due to invalid credentials: ${error}`, { error }) + throw error + } else { + logger.info( + "Falling back to database server-level connection (database might not exist)..." + ) + const serverLevelConfig = buildKnexConfig({ connection: { database: "" } }) + dbMigrationClient = knex(serverLevelConfig) + i -= 1 + isDatabaseSocketReady = true + continue + } + } else if (isMissingDatabaseFailure(error)) { + if (isDatabaseSocketReady) { + logger.error(`Database connection failed because database does not exist): ${error}`, { + error, + }) + throw error + } else { + logger.info( + "Falling back to default postgres connection (after database does not exist failure)..." + ) + const serverLevelConfig = buildKnexConfig({ connection: { database: "postgres" } }) + dbMigrationClient = knex(serverLevelConfig) + i -= 1 + isDatabaseSocketReady = true + continue + } + } else { + logger.error(`Unknown database connection error: ${error}`, { error }) + //throw error + } + } + } + + throw new Error(`Failed to connect to the database due to timeout.`) +} + +export default waitForDatabase diff --git a/api/src/initializers/10-ensure-database.ts b/api/src/initializers/10-ensure-database.ts new file mode 100644 index 0000000..0b8645f --- /dev/null +++ b/api/src/initializers/10-ensure-database.ts @@ -0,0 +1,71 @@ +import knex, { type Knex } from "knex" + +import { logger } from "@/utils/logger" +import { isCredentialFailure, isMissingDatabaseFailure } from "@/utils/db-error-helpers" +import { buildKnexConfig } from "@/db/db-migration-client" +import { DB_DATABASE } from "@/config" + +async function databaseExists(dbMigrationClient: Knex, databaseName: string): Promise { + const result = await dbMigrationClient.raw("SELECT 1 FROM pg_database WHERE datname = ?", [ + databaseName, + ]) + + return result.rows.length > 0 +} + +async function createDatabase(): Promise { + logger.info("Attempting direct to database connection to determine if database exists...") + const databaseConfig = buildKnexConfig() + let dbMigrationClient = knex(databaseConfig) + let isCredentialFailureError = false + let isMissingDatabaseError = false + + try { + if (await databaseExists(dbMigrationClient, DB_DATABASE)) { + return true + } + } catch (error) { + if (isCredentialFailure(error)) { + isCredentialFailureError = true + logger.info("Database connection failed due to invalid credential, retrying...") + } + if (isMissingDatabaseFailure(error)) { + isMissingDatabaseError = true + logger.info("Database connection failed due missing default database, retrying...") + } else { + logger.error(`Unknown connection failure, could not determine if database exists: ${error}`, { + error, + }) + throw error + } + } + + if (isCredentialFailureError || isMissingDatabaseError) { + logger.info("Attempting server-level connection to determine if database exists...") + const serverLevelConfig = buildKnexConfig({ connection: { database: "" } }) + dbMigrationClient = knex(serverLevelConfig) + try { + if (await databaseExists(dbMigrationClient, DB_DATABASE)) { + return true + } + } catch (error) { + logger.error( + `Could not determine if database exists database with server-level connection: ${error}`, + { error } + ) + throw error + } + } + + logger.info(`Database ${DB_DATABASE} does not exist: creating...`) + try { + await dbMigrationClient.raw(`CREATE DATABASE ${DB_DATABASE}`) + } catch (error) { + logger.error(`Failed to create database: ${error}`, { error }) + throw error + } + + return true +} + +export default createDatabase diff --git a/api/src/initializers/20-run-migrations.ts b/api/src/initializers/20-run-migrations.ts new file mode 100644 index 0000000..db436e8 --- /dev/null +++ b/api/src/initializers/20-run-migrations.ts @@ -0,0 +1,31 @@ +import dbMigrationClient from "@/db/db-migration-client" +import { logger } from "@/utils/logger" + +type MigrationInfo = { + file: string + directory: string +} + +async function runMigrations(): Promise { + const [_completedMigrations, pendingMigrations]: [MigrationInfo[], MigrationInfo[]] = + await dbMigrationClient.migrate.list() + + if (pendingMigrations.length === 0) { + logger.info("No pending migrations.") + return + } + + for (const { file, directory } of pendingMigrations) { + logger.info(`Running migration: ${directory}/${file}`) + try { + await dbMigrationClient.migrate.up() + } catch (error) { + logger.error(`Error running migration: ${error}`, { error }) + throw error + } + } + + logger.info("All migrations completed successfully.") +} + +export default runMigrations diff --git a/api/src/initializers/30-run-seeds.ts b/api/src/initializers/30-run-seeds.ts new file mode 100644 index 0000000..0a03528 --- /dev/null +++ b/api/src/initializers/30-run-seeds.ts @@ -0,0 +1,25 @@ +import { logger } from "@/utils/logger" +import dbMigrationClient from "@/db/db-migration-client" +import { User } from "@/models" + +export async function runSeeds(): Promise { + if (process.env.SKIP_SEEDING_UNLESS_EMPTY === "true") { + const count = await User.count({ logging: false }) + + if (count > 0) { + logger.warn("Skipping seeding as SKIP_SEEDING_UNLESS_EMPTY set, and data already seeded.") + return + } + } + + try { + await dbMigrationClient.seed.run() + } catch (error) { + logger.error(`Error running seeds: ${error}`, { error }) + throw error + } + + return +} + +export default runSeeds diff --git a/api/src/initializers/index.ts b/api/src/initializers/index.ts new file mode 100644 index 0000000..7f6c625 --- /dev/null +++ b/api/src/initializers/index.ts @@ -0,0 +1,39 @@ +import { logger } from "@/utils/logger" +import * as fs from "fs/promises" +import * as path from "path" + +const NON_INITIALIZER_REGEX = /^index\.(ts|js)$/ + +export async function importAndExecuteInitializers() { + const files = await fs.readdir(__dirname) + + for (const file of files) { + if (NON_INITIALIZER_REGEX.test(file)) continue + + const modulePath = path.join(__dirname, file) + logger.info(`Running initializer: ${modulePath}`) + + try { + const { default: initializerAction } = await require(modulePath) + await initializerAction() + } catch (error) { + logger.error(`Failed to run initializer: ${modulePath}`, { error }) + throw error + } + } + + return true +} + +if (require.main === module) { + // TODO: add some kind of middleware that 503s? if initialization failed? + ;(async () => { + try { + await importAndExecuteInitializers() + } catch { + logger.error("Failed to complete initialization!") + } + + process.exit(0) + })() +} diff --git a/api/src/integrations/README.md b/api/src/integrations/README.md new file mode 100644 index 0000000..270ab43 --- /dev/null +++ b/api/src/integrations/README.md @@ -0,0 +1,4 @@ +# api/src/integrations/README.md + +Integrations are api integrations with external services. +They might package a bunch of api calls, or just one. diff --git a/api/src/integrations/auth0-integration.ts b/api/src/integrations/auth0-integration.ts new file mode 100644 index 0000000..042b61a --- /dev/null +++ b/api/src/integrations/auth0-integration.ts @@ -0,0 +1,56 @@ +import axios from "axios" + +import { AUTH0_DOMAIN } from "@/config" + +const auth0Api = axios.create({ + baseURL: AUTH0_DOMAIN, +}) + +export interface Auth0UserInfo { + email: string + firstName: string + lastName: string + auth0Subject: string +} + +export interface Auth0Response { + sub: string // "auth0|6241ec44e5b4a700693df293" + given_name: string // "Jane" + family_name: string // "Doe" + nickname: string // "Jane" + name: string // "Jane Doe" + picture: string // https://s.gravatar.com/avatar/1234567890abcdef1234567890abcdef?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fmb.png + updated_at: string // "2023-10-30T17:25:52.975Z" + email: string // "janedoe@gmail.com" + email_verified: boolean // true + oid?: string // 11111111-2222-3333-4444-555555555555" +} + +export class Auth0PayloadError extends Error { + constructor(data: unknown) { + super(`Payload from Auth0 is strange or failed for: ${JSON.stringify(data)}`) + this.name = "Auth0PayloadError" + } +} + +export const auth0Integration = { + async getUserInfo(token: string): Promise { + const { data }: { data: Auth0Response } = await auth0Api.get("/userinfo", { + headers: { authorization: token }, + }) + + const firstName = data.given_name || "UNKNOWN" + const lastName = data.family_name || "UNKNOWN" + const fallbackEmail = `${firstName}.${lastName}@yukon-no-email.ca` + const email = data.email || fallbackEmail + + return { + auth0Subject: data.sub, + email, + firstName, + lastName, + } + }, +} + +export default auth0Integration diff --git a/api/src/integrations/index.ts b/api/src/integrations/index.ts new file mode 100644 index 0000000..e898355 --- /dev/null +++ b/api/src/integrations/index.ts @@ -0,0 +1,5 @@ +export { + auth0Integration, + Auth0PayloadError, + type Auth0UserInfo, +} from "./auth0-integration" \ No newline at end of file diff --git a/api/src/jobs/base-job.ts b/api/src/jobs/base-job.ts new file mode 100644 index 0000000..f675d7e --- /dev/null +++ b/api/src/jobs/base-job.ts @@ -0,0 +1,38 @@ +import logger from "@/utils/logger" + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type HasNoArgsConstructor = T extends { new (): any } ? true : false + +type CleanConstructorParameters = + HasNoArgsConstructor extends true ? [] : ConstructorParameters + +export class BaseJob { + protected filename: string + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(...args: any[]) { + this.filename = args[0] || __filename + } + + static perform( + this: T, + ...args: CleanConstructorParameters + ): ReturnType["perform"]> { + try { + const instance = new this(...args) + const { filename } = instance + logger.debug(`## Performing Job: ${filename}`) + return instance.perform() + } catch (error) { + logger.error(`Failed to perform job: ${error}`, { error }) + throw new Error(`Failed to perform job: ${error}`) + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + perform(): any { + throw new Error("Not Implemented") + } +} + +export default BaseJob diff --git a/api/src/jobs/index.ts b/api/src/jobs/index.ts new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/api/src/jobs/index.ts @@ -0,0 +1 @@ + diff --git a/api/src/middlewares/README.md b/api/src/middlewares/README.md new file mode 100644 index 0000000..0eb3d0b --- /dev/null +++ b/api/src/middlewares/README.md @@ -0,0 +1,3 @@ +# api/src/middlewares/README.md + +Middleware are actions that run before or after every request. diff --git a/api/src/middlewares/authorization-middleware.ts b/api/src/middlewares/authorization-middleware.ts new file mode 100644 index 0000000..eb6ccdd --- /dev/null +++ b/api/src/middlewares/authorization-middleware.ts @@ -0,0 +1,53 @@ +import { type NextFunction, type Response } from "express" +import { type Request as JwtRequest } from "express-jwt" +import { isNil } from "lodash" + +import { logger } from "@/utils/logger" +import { Auth0PayloadError } from "@/integrations/auth0-integration" +import { User } from "@/models" +import { Users } from "@/services" + +export type AuthorizationRequest = JwtRequest & { + currentUser?: User | null +} + +/** + * Requires api/src/middlewares/jwt-middleware.ts to be run first + * + * I'd love to merge that code in here at some point, or make all this code a controller "before action" + * I'm uncomfortable with creating users automatically here, I'd rather the front-end requested + * user creation directly, and might switch to that in the future. + * + * NOTE: must be kept in sync with api/tests/support/mock-current-user.ts + */ +export async function authorizationMiddleware( + req: AuthorizationRequest, + res: Response, + next: NextFunction +) { + const user = await User.withScope(["asCurrentUser"]).findOne({ + where: { + auth0Subject: req.auth?.sub || "", + }, + }) + + if (!isNil(user)) { + req.currentUser = user + return next() + } + + try { + const token = req.headers.authorization || "" + const user = await Users.EnsureFromAuth0TokenService.perform(token) + req.currentUser = user + return next() + } catch (error) { + logger.error(`Error ensuring user from Auth0 token ${error}`, { error }) + + if (error instanceof Auth0PayloadError) { + return res.status(502).json({ message: "External authorization api failed." }) + } else { + return res.status(401).json({ message: "User authentication failed." }) + } + } +} diff --git a/api/src/middlewares/index.ts b/api/src/middlewares/index.ts new file mode 100644 index 0000000..336dc5c --- /dev/null +++ b/api/src/middlewares/index.ts @@ -0,0 +1,3 @@ +export { authorizationMiddleware } from "./authorization-middleware" +export { jwtMiddleware } from "./jwt-middleware" +export { requestLoggerMiddleware } from "./request-logger-middleware" diff --git a/api/src/middlewares/jwt-middleware.ts b/api/src/middlewares/jwt-middleware.ts new file mode 100644 index 0000000..bb7d33b --- /dev/null +++ b/api/src/middlewares/jwt-middleware.ts @@ -0,0 +1,26 @@ +import { expressjwt as jwt } from "express-jwt" +import jwksRsa, { type GetVerificationKey } from "jwks-rsa" + +import { AUTH0_DOMAIN, AUTH0_AUDIENCE, NODE_ENV } from "@/config" +import { logger } from "@/utils/logger" + +if (NODE_ENV !== "test") { + logger.info(`AUTH0_DOMAIN - ${AUTH0_DOMAIN}/.well-known/jwks.json`) +} + +// TODO: investigate converting this to an integration or utility of the authorization middleware +export const jwtMiddleware = jwt({ + secret: jwksRsa.expressJwtSecret({ + cache: true, + rateLimit: true, + jwksRequestsPerMinute: 5, + jwksUri: `${AUTH0_DOMAIN}/.well-known/jwks.json`, + }) as GetVerificationKey, + + // Validate the audience and the issuer. + audience: AUTH0_AUDIENCE, + issuer: [`${AUTH0_DOMAIN}/`], + algorithms: ["RS256"], +}) + +export default jwtMiddleware diff --git a/api/src/middlewares/request-logger-middleware.ts b/api/src/middlewares/request-logger-middleware.ts new file mode 100644 index 0000000..200eef2 --- /dev/null +++ b/api/src/middlewares/request-logger-middleware.ts @@ -0,0 +1,14 @@ +import morgan from "morgan" + +import logger from "@/utils/logger" + +export const requestLoggerMiddleware = morgan( + ":method :url :status :res[content-length] - :response-time ms", + { + stream: { + write: (message) => logger.http(message.trim()), + }, + } +) + +export default requestLoggerMiddleware diff --git a/api/src/models/base-model.ts b/api/src/models/base-model.ts new file mode 100644 index 0000000..301ace9 --- /dev/null +++ b/api/src/models/base-model.ts @@ -0,0 +1,147 @@ +import { + AttributeNames, + Attributes, + CreationOptional, + FindOptions, + Model, + ModelStatic, + Op, + WhereOptions, +} from "@sequelize/core" + +import { searchFieldsByTermsFactory } from "@/utils/search-fields-by-terms-factory" + +// See api/node_modules/@sequelize/core/lib/model.d.ts -> Model +export abstract class BaseModel< + // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any + TModelAttributes extends {} = any, + // eslint-disable-next-line @typescript-eslint/ban-types + TCreationAttributes extends {} = TModelAttributes, +> extends Model { + declare id: CreationOptional + + static addSearchScope(this: ModelStatic, fields: AttributeNames[]) { + const searchScopeFunction = searchFieldsByTermsFactory(fields) + this.addScope("search", searchScopeFunction) + } + + // static findByPk>( + // this: ModelStatic, + // identifier: unknown, + // options: FindByPkOptions & { raw: true; rejectOnEmpty?: false }, + // ): Promise; + // static findByPk>( + // this: ModelStatic, + // identifier: unknown, + // options: NonNullFindByPkOptions & { raw: true }, + // ): Promise; + // static findByPk( + // this: ModelStatic, + // identifier: unknown, + // options: NonNullFindByPkOptions, + // ): Promise; + // static findByPk( + // this: ModelStatic, + // identifier: unknown, + // options?: FindByPkOptions, + // ): Promise; + public static async findByIdentifierOrPk( + this: ModelStatic, + identifierOrPk: string | number, + options?: Omit>, "where"> + ): Promise { + if (typeof identifierOrPk === "number" || !isNaN(Number(identifierOrPk))) { + const primaryKey = identifierOrPk + return this.findByPk(primaryKey, options) + } + + const identifier = identifierOrPk + if (!("identifier" in this.getAttributes())) { + throw new Error(`${this.name} does not have a 'identifier' attribute.`) + } + + return this.findOne({ + ...options, + // @ts-expect-error - We know that the model has a slug attribute, and are ignoring the TS error + where: { identifier }, + }) + } + + // See api/node_modules/@sequelize/core/lib/model.d.ts -> findAll + // Taken from https://api.rubyonrails.org/v7.1.0/classes/ActiveRecord/Batches.html#method-i-find_each + // Enforces sort by id, overwriting any supplied order + public static async findEach( + this: ModelStatic, + processFunction: (record: M) => Promise + ): Promise + public static async findEach>( + this: ModelStatic, + options: Omit>, "raw"> & { + raw: true + batchSize?: number + }, + processFunction: (record: R) => Promise + ): Promise + public static async findEach( + this: ModelStatic, + options: FindOptions> & { + batchSize?: number + }, + processFunction: (record: M) => Promise + ): Promise + public static async findEach>( + this: ModelStatic, + optionsOrFunction: + | ((record: M) => Promise) + | (Omit>, "raw"> & { raw: true; batchSize?: number }) + | (FindOptions> & { batchSize?: number }), + maybeFunction?: (record: R | M) => Promise + ): Promise { + let options: + | (FindOptions> & { batchSize?: number }) + | (Omit>, "raw"> & { raw: true; batchSize?: number }) + + // TODO: fix types so that process function is M when not raw + // and R when raw. Raw is usable, just incorrectly typed. + let processFunction: (record: M) => Promise + + if (typeof optionsOrFunction === "function") { + options = {} + processFunction = optionsOrFunction + } else if (maybeFunction === undefined) { + throw new Error("findEach requires a processFunction") + } else { + options = optionsOrFunction + processFunction = maybeFunction + } + + const batchSize = options.batchSize ?? 1000 + let lastId = 0 + let continueProcessing = true + + while (continueProcessing) { + // TODO: fix where option types so cast is not needed + const whereClause = { + ...options.where, + id: { [Op.gt]: lastId }, + } as WhereOptions> + const records = await this.findAll({ + ...options, + where: whereClause, + limit: batchSize, + order: [["id", "ASC"]], + }) + + for (const record of records) { + await processFunction(record) + lastId = record.id + } + + if (records.length < batchSize) { + continueProcessing = false + } + } + } +} + +export default BaseModel diff --git a/api/src/models/index.ts b/api/src/models/index.ts new file mode 100644 index 0000000..a67a441 --- /dev/null +++ b/api/src/models/index.ts @@ -0,0 +1,19 @@ +import db from "@/db/db-client" + +// Models +import User, { UserRoles } from "@/models/user" + +db.addModels([ + User, +]) + +// Lazy load scopes +User.establishScopes() + +export { + User, + UserRoles, +} + +// Special db instance will all models loaded +export default db diff --git a/api/src/models/user.ts b/api/src/models/user.ts new file mode 100644 index 0000000..7f58369 --- /dev/null +++ b/api/src/models/user.ts @@ -0,0 +1,110 @@ +import { + type CreationOptional, + DataTypes, + InferAttributes, + InferCreationAttributes, + type NonAttribute, + sql, +} from "@sequelize/core" +import { + Attribute, + AutoIncrement, + Default, + Index, + NotNull, + PrimaryKey, + ValidateAttribute, +} from "@sequelize/core/decorators-legacy" +import { isArray, isNil } from "lodash" + +import BaseModel from "@/models/base-model" + +/** Keep in sync with web/src/api/users-api.ts */ +export enum UserRoles { + SYSTEM_ADMIN = "system_admin", + USER = "user", +} + +export class User extends BaseModel, InferCreationAttributes> { + static readonly Roles = UserRoles + + @Attribute(DataTypes.INTEGER) + @PrimaryKey + @AutoIncrement + declare id: CreationOptional + + @Attribute(DataTypes.STRING(100)) + @NotNull + @Index({ unique: true }) + declare email: string + + @Attribute(DataTypes.STRING(100)) + @NotNull + @Index({ unique: true }) + declare auth0Subject: string + + @Attribute(DataTypes.STRING(100)) + @NotNull + declare firstName: string + + @Attribute(DataTypes.STRING(100)) + @NotNull + declare lastName: string + + @Attribute(DataTypes.STRING(200)) + @NotNull + declare displayName: string + + @Attribute({ + type: DataTypes.STRING(255), + get() { + const roles = this.getDataValue("roles") + if (isNil(roles)) { + return [] + } + return roles.split(",") + }, + set(value: string[]) { + this.setDataValue("roles", value.join(",")) + }, + }) + @NotNull + @ValidateAttribute({ + validator: (valueString: string | string[]) => { + const value = isArray(valueString) ? valueString : valueString.split(",") + const validRoles = Object.values(UserRoles) as string[] + const invalidRoles = value.filter((role) => !validRoles.includes(role)) + if (invalidRoles.length > 0) throw new Error(`Invalid role: ${invalidRoles.join(", ")}`) + }, + }) + declare roles: UserRoles[] + + @Attribute(DataTypes.DATE(0)) + @NotNull + @Default(sql.literal("CURRENT_TIMESTAMP")) + declare createdAt: CreationOptional + + @Attribute(DataTypes.DATE(0)) + @NotNull + @Default(sql.literal("CURRENT_TIMESTAMP")) + declare updatedAt: CreationOptional + + @Attribute(DataTypes.DATE(0)) + declare deletedAt: Date | null + + // Magic Attributes + get isSystemAdmin(): NonAttribute { + return this.roles.some((role) => role === UserRoles.SYSTEM_ADMIN) + } + + // Associations + + // Scopes + static establishScopes(): void { + this.addSearchScope(["firstName", "lastName", "displayName", "email"]) + + this.addScope("asCurrentUser", {}) + } +} + +export default User diff --git a/api/src/policies/README.md b/api/src/policies/README.md new file mode 100644 index 0000000..8dd007f --- /dev/null +++ b/api/src/policies/README.md @@ -0,0 +1,254 @@ +# Policies + +Policies are used to control access to data in a controller, before it is returned to the client. +Polices can be used in the following ways: + +1. Build a policy instance and check the controller action matching boolean function. + Controller#update -> Policy#update + + ```ts + export class AccessGrantsController extends BaseController { + async update() { + const accessGrant = await this.loadAccessGrant() + if (isNil(accessGrant)) { + return this.response.status(404).json({ message: "Access grant not found." }) + } + + const policy = this.buildPolicy(accessGrant) + if (!policy.update()) { + return this.response + .status(403) + .json({ message: "You are not authorized to update access grants on this dataset." }) + } + + const permittedAttributes = policy.permitAttributesForUpdate(this.request.body) + try { + const updatedAccessGrant = await UpdateService.perform( + accessGrant, + permittedAttributes, + this.currentUser + ) + return this.response.status(200).json({ accessGrant: updatedAccessGrant }) + } catch (error) { + return this.response.status(422).json({ message: `Access grant update failed: ${error}` }) + } + } + + private async loadAccessGrant(): Promise { + return AccessGrant.findByPk(this.params.accessGrantId) + } + + private buildPolicy(accessGrant: AccessGrant) { + return new AccessGrantsPolicy(this.currentUser, accessGrant) + } + } + ``` + +2. The previous example also demostrates a second way of using policies. The "permitted attributes" pattern. A policy can also be used to provide an "allow list" of attributes that a user is allowed to submit for a given controller action. + + ```ts + export class AccessGrantsPolicy extends BasePolicy { + permittedAttributes(): Path[] { + return ["supportId", "grantLevel", "accessType", "isProjectDescriptionRequired"] + } + } + ``` + +3. Policies can also be used to restrict the results of an "index" or list action in a controller. + In this case a bunch of scoping conditions are built up, and then passed to the "apply scope" function. This produces a query that, when executed, will only return the records that the current user is allowed to see. + + ```ts + export class AccessGrantsController extends BaseController { + async index() { + const where = this.buildWhere() + const scopes = this.buildFilterScopes() + const scopedAccessGrants = AccessGrantsPolicy.applyScope(scopes, this.currentUser) + + const totalCount = await scopedAccessGrants.count({ where }) + const accessGrants = await scopedAccessGrants.findAll({ + where, + limit: this.pagination.limit, + offset: this.pagination.offset, + }) + + return this.response.json({ accessGrants, totalCount }) + } + } + ``` + +## Policy#policyScope + +The `policyScope` method is used to add a scope to the given model. This scope is permanently added to the model, though it likely shouldn't be used outside of the policy. + +i.e. + +```ts +export class AccessRequestsPolicy extends PolicyFactory(AccessRequest) { + static policyScope(user: User): FindOptions> { + if (user.isSystemAdmin || user.isBusinessAnalyst) { + return {} + } + + if (user.isDataOwner) { + return { + include: [ + { + association: "dataset", + where: { + ownerId: user.id, + }, + }, + ], + } + } + + return { + where: { + requestorId: user.id, + }, + } + } +} +``` + +can be considered equivalent to + +```ts +AccessReqeuest.addScope("policyScope", (user: User) => { + if (user.isSystemAdmin || user.isBusinessAnalyst) { + return {} + } + + if (user.isDataOwner) { + return { + include: [ + { + association: "dataset", + where: { + ownerId: user.id, + }, + }, + ], + } + } + + return { + where: { + requestorId: user.id, + }, + } +}) +``` + +# Full Example + +Here is a simple example of a controller using a policy to control access to a resource. +The full cases might be more complex, but the "policy" pattern leaves space for that complexity to exist without cluttering the controller. + +```ts +export class AccessGrantsController extends BaseController { + async index() { + const where = this.buildWhere() + const scopes = this.buildFilterScopes() + const scopedAccessGrants = AccessGrantsPolicy.applyScope(scopes, this.currentUser) + + const totalCount = await scopedAccessGrants.count({ where }) + const accessGrants = await scopedAccessGrants.findAll({ + where, + limit: this.pagination.limit, + offset: this.pagination.offset, + }) + + return this.response.json({ accessGrants, totalCount }) + } + + async create() { + const accessGrant = await this.buildAccessGrant() + if (isNil(accessGrant)) { + return this.response.status(404).json({ message: "Dataset not found." }) + } + + const policy = this.buildPolicy(accessGrant) + if (!policy.create()) { + return this.response + .status(403) + .json({ message: "You are not authorized to add access grants for this dataset." }) + } + + const permittedAttributes = policy.permitAttributesForCreate(this.request.body) + try { + const accessGrant = await CreateService.perform(permittedAttributes, this.currentUser) + return this.response.status(201).json({ accessGrant }) + } catch (error) { + return this.response.status(422).json({ message: `Access grant creation failed: ${error}` }) + } + } + + async update() { + const accessGrant = await this.loadAccessGrant() + if (isNil(accessGrant)) { + return this.response.status(404).json({ message: "Access grant not found." }) + } + + const policy = this.buildPolicy(accessGrant) + if (!policy.update()) { + return this.response + .status(403) + .json({ message: "You are not authorized to update access grants on this dataset." }) + } + + const permittedAttributes = policy.permitAttributesForUpdate(this.request.body) + try { + const updatedAccessGrant = await UpdateService.perform( + accessGrant, + permittedAttributes, + this.currentUser + ) + return this.response.status(200).json({ accessGrant: updatedAccessGrant }) + } catch (error) { + return this.response.status(422).json({ message: `Access grant update failed: ${error}` }) + } + } + + private async buildAccessGrant(): Promise { + return AccessGrant.build(this.request.body) + } + + private async loadAccessGrant(): Promise { + return AccessGrant.findByPk(this.params.accessGrantId) + } + + private buildPolicy(accessGrant: AccessGrant) { + return new AccessGrantsPolicy(this.currentUser, accessGrant) + } +} +``` + +and the policy + +```ts +export class AccessGrantsPolicy extends BasePolicy { + create(): boolean { + // some code that might returns true + return false + } + + update(): boolean { + // some code that might returns true + return false + } + + destroy(): boolean { + // some code that might returns true + return false + } + + permittedAttributes(): Path[] { + return ["supportId", "grantLevel", "accessType", "isProjectDescriptionRequired"] + } + + permittedAttributesForCreate(): Path[] { + return ["datasetId", ...this.permittedAttributes()] + } +} +``` diff --git a/api/src/policies/base-policy.ts b/api/src/policies/base-policy.ts new file mode 100644 index 0000000..363506e --- /dev/null +++ b/api/src/policies/base-policy.ts @@ -0,0 +1,146 @@ +import { ModelStatic, Model, Attributes, FindOptions, ScopeOptions, literal } from "@sequelize/core" + +import { User } from "@/models" +import { Path, deepPick } from "@/utils/deep-pick" +import { isInteger, isNil } from "lodash" + +export type Actions = "show" | "create" | "update" | "destroy" + +export const NO_RECORDS_SCOPE = { where: literal("1 = 0") } +export const ALL_RECORDS_SCOPE = {} + +/** + * See PolicyFactory below for policy with scope helpers + */ +export class BasePolicy { + protected user: User + protected record: M + + constructor(user: User, record: M) { + this.user = user + this.record = record + } + + show(): boolean { + return false + } + + create(): boolean { + return false + } + + update(): boolean { + return false + } + + destroy(): boolean { + return false + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + static policyScope(user: User, ...args: unknown[]): FindOptions> { + throw new Error("Derived classes must implement policyScope method") + } + + permitAttributes(record: Partial): Partial { + return deepPick(record, this.permittedAttributes()) + } + + permitAttributesForCreate(record: Partial): Partial { + if (this.permittedAttributesForCreate !== BasePolicy.prototype.permittedAttributesForCreate) { + return deepPick(record, this.permittedAttributesForCreate()) + } else { + return deepPick(record, this.permittedAttributes()) + } + } + + permitAttributesForUpdate(record: Partial): Partial { + if (this.permittedAttributesForUpdate !== BasePolicy.prototype.permittedAttributesForUpdate) { + return deepPick(record, this.permittedAttributesForUpdate()) + } else { + return deepPick(record, this.permittedAttributes()) + } + } + + permittedAttributes(): Path[] { + throw new Error("Not Implemented") + } + + permittedAttributesForCreate(): Path[] { + throw new Error("Not Implemented") + } + + permittedAttributesForUpdate(): Path[] { + throw new Error("Not Implemented") + } + + setNumberNullIfEmpty(input: number | null | undefined): number | null | undefined { + let output = input + if (!isNil(input) && !isInteger(input)) output = undefined + if (!isNil(input) && input == 0) output = undefined + return output + } + + setNumberZeroIfEmpty(input: number | null | undefined): number { + let output = input + if (!isNil(input) && !isInteger(input)) output = 0 + if (!isNil(input) && input == 0) output = 0 + return output ?? 0 + } + /** + * Add to support return policy information via this.reponse.json({ someObject, policy }) + * + * If this method becomes complex, it should be broken out into a serializer. + * + * @returns a JSON representation of the policy + */ + toJSON(): Record { + return { + show: this.show(), + create: this.create(), + update: this.update(), + destroy: this.destroy(), + } + } +} + +// See api/node_modules/sequelize/types/model.d.ts -> Model -> scope +export type BaseScopeOptions = string | ScopeOptions + +export const POLICY_SCOPE_NAME = "policyScope" + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AllArgsButFirstOne = T extends [any, ...infer Rest] ? Rest : never + +export function PolicyFactory(modelClass: ModelStatic) { + const policyClass = class Policy extends BasePolicy { + static applyScope

( + this: P, + scopes: BaseScopeOptions[], + user: User, + ...extraPolicyScopeArgs: AllArgsButFirstOne> + ): ModelStatic { + this.ensurePolicyScope() + return modelClass.withScope([ + ...scopes, + { method: [POLICY_SCOPE_NAME, user, ...extraPolicyScopeArgs] }, + ]) + } + + /** + * Just in time scope creation for model class. + * TODO: to have scope creation occur at definition time, instead of execution time. + */ + static ensurePolicyScope() { + if (Object.prototype.hasOwnProperty.call(modelClass.options.scopes, POLICY_SCOPE_NAME)) { + return + } + + modelClass.addScope(POLICY_SCOPE_NAME, this.policyScope.bind(modelClass)) + } + } + + return policyClass +} + +export default PolicyFactory diff --git a/api/src/policies/index.ts b/api/src/policies/index.ts new file mode 100644 index 0000000..7021cf6 --- /dev/null +++ b/api/src/policies/index.ts @@ -0,0 +1,3 @@ +// Policy Bundles +export { type BaseScopeOptions } from "./base-policy" +export { UsersPolicy } from "./users-policy" diff --git a/api/src/policies/users-policy.ts b/api/src/policies/users-policy.ts new file mode 100644 index 0000000..edca2d3 --- /dev/null +++ b/api/src/policies/users-policy.ts @@ -0,0 +1,79 @@ +import { Attributes, FindOptions } from "@sequelize/core" + +import { Path } from "@/utils/deep-pick" +import { User } from "@/models" +import { ALL_RECORDS_SCOPE, PolicyFactory } from "@/policies/base-policy" + +export class UsersPolicy extends PolicyFactory(User) { + show(): boolean { + if (this.user.isSystemAdmin) { + return true + } + + if (this.user.id === this.record.id) { + return true + } + + return false + } + + create(): boolean { + if (this.user.isSystemAdmin) { + return true + } + + return false + } + + update(): boolean { + if (this.user.isSystemAdmin) { + return true + } + + if (this.user.id === this.record.id) { + return true + } + + return false + } + + destroy(): boolean { + if (this.user.id === this.record.id) { + return false + } + + if (this.user.isSystemAdmin) { + return true + } + + return false + } + + permittedAttributes(): Path[] { + const attributes: (keyof Attributes)[] = [ + "email", + "auth0Subject", + "firstName", + "lastName", + "displayName", + ] + + return attributes + } + + permittedAttributesForCreate(): Path[] { + return [...this.permittedAttributes()] + } + + permittedAttributesForUpdate(): Path[] { + return [...this.permittedAttributes()] + } + + static policyScope(user: User): FindOptions> { + if (user.isSystemAdmin) return ALL_RECORDS_SCOPE + + return { where: { id: user.id } } + } +} + +export default UsersPolicy diff --git a/api/src/router.ts b/api/src/router.ts new file mode 100644 index 0000000..f00ec6d --- /dev/null +++ b/api/src/router.ts @@ -0,0 +1,91 @@ +import path from "path" +import fs from "fs" + +import { + Router, + type Request, + type Response, + type ErrorRequestHandler, + type NextFunction, +} from "express" +import { UnauthorizedError } from "express-jwt" +import { template } from "lodash" + +import { APPLICATION_NAME, GIT_COMMIT_HASH, NODE_ENV, RELEASE_TAG } from "@/config" +import { logger } from "@/utils/logger" + +import { jwtMiddleware, authorizationMiddleware } from "@/middlewares" + +import { CurrentUserController, UsersController } from "@/controllers" + +export const router = Router() + +// non-api (no authentication is required) routes +router.route("/_status").get((_req: Request, res: Response) => { + return res.json({ + RELEASE_TAG, + GIT_COMMIT_HASH, + }) +}) + +// external (public) routes - no authentication required + +// api routes +router.use("/api", jwtMiddleware, authorizationMiddleware) + +router.route("/api/current-user").get(CurrentUserController.show) + +router.route("/api/users").get(UsersController.index).post(UsersController.create) +router + .route("/api/users/:userId") + .get(UsersController.show) + .patch(UsersController.update) + .delete(UsersController.destroy) + +// if no other routes match, return a 404 +router.use("/api", (req: Request, res: Response) => { + return res.status(404).json({ message: "Not Found", url: req.path }) +}) + +// Special error handler for all api errors +// See https://expressjs.com/en/guide/error-handling.html#writing-error-handlers +router.use("/api", (err: ErrorRequestHandler, _req: Request, res: Response, next: NextFunction) => { + if (res.headersSent) { + return next(err) + } + + if (err instanceof UnauthorizedError) { + logger.error(err) + return res.status(err.status).json({ message: err.inner.message }) + } + + /* if (err instanceof DatabaseError) { + logger.error(err) + return res.status(422).json({ message: "Invalid query against database." }) + } + */ + logger.error(err) + return res.status(500).json({ message: "Internal Server Error" }) +}) + +// if no other non-api routes match, send the pretty 404 page +if (NODE_ENV == "development") { + router.use("/", (_req: Request, res: Response) => { + const templatePath = path.resolve(__dirname, "web/404.html") + try { + const templateString = fs.readFileSync(templatePath, "utf8") + const compiledTemplate = template(templateString) + const result = compiledTemplate({ + applicationName: APPLICATION_NAME, + releaseTag: RELEASE_TAG, + gitCommitHash: GIT_COMMIT_HASH, + }) + return res.status(404).send(result) + } catch (error) { + logger.error(error) + return res.status(500).send(`Error building 404 page: ${error}`) + } + }) +} + +export default router diff --git a/api/src/scheduler.ts b/api/src/scheduler.ts new file mode 100644 index 0000000..ead5f16 --- /dev/null +++ b/api/src/scheduler.ts @@ -0,0 +1,38 @@ +import { CronJob } from "cron" + +import { API_PORT, APPLICATION_NAME } from "@/config" +import logger from "@/utils/logger" +import withLoggingFactory from "@/utils/with-logging-factory" + +logger.info(`${APPLICATION_NAME} JOBS listenting on port ${API_PORT}`) + +/** + * See https://www.npmjs.com/package/cron. + * + * Most useful debugging option is `runOnInit: true`, which will immediately executes the job. + * + * Allowed fields + * # ┌────────────── second (optional) + * # │ ┌──────────── minute + * # │ │ ┌────────── hour + * # │ │ │ ┌──────── day of month + * # │ │ │ │ ┌────── month + * # │ │ │ │ │ ┌──── day of week + * # │ │ │ │ │ │ + * # │ │ │ │ │ │ + * # * * * * * * + */ + +export async function enqueueJobs() {} + +if (require.main === module) { + ;(async () => { + logger.debug("Enqueuing jobs...") + try { + await enqueueJobs() + } catch { + logger.error("Failed to enqueue jobs!") + process.exit(1) + } + })() +} diff --git a/api/src/serializers/README.md b/api/src/serializers/README.md new file mode 100644 index 0000000..49893af --- /dev/null +++ b/api/src/serializers/README.md @@ -0,0 +1,71 @@ +# Serializers + +Serializers take model data, and add or remove fields. They are used to convert a database representation of a model to a front-end representation of a model. This might include removing fields that should not be exposed to the front-end, or adding fields that are derived from the database representation. + +Serializers are used in controllers to convert from a database representation to a front-end data packet. Serializers should not be used for general data formating such as date or money formatting, as formatting those kinds of things in the front-end is generally more flexible. + +e.g. Usage in a Controller might look like this +Note that the BaseSerializer supports passing either an array or a single model to the `perform` method. + +```typescript +import { isNil } from "lodash" + +import logger from "@/utils/logger" +import { User } from "@/models" +import { UsersPolicy } from "@/policies" +import { CreateService } from "@/services/users" +import { IndexSerializer } from "@/serializers/users" +import BaseController from "@/controllers/base-controller" + +export class FormsController extends BaseController { + async index() { + try { + const where = this.buildWhere() + const scopes = this.buildFilterScopes() + const scopedUsers = UsersPolicy.applyScope(scopes, this.currentUser) + + const totalCount = await scopedUsers.count({ where }) + const users = await scopedUsers.findAll({ + where, + limit: this.pagination.limit, + offset: this.pagination.offset, + }) + const serializedUsers = IndexSerializer.perform(users) + return this.response.json({ + users: serializedUsers, + totalCount, + }) + } catch (error) { + logger.error("Error fetching users" + error) + return this.response.status(400).json({ + message: `Error fetching users: ${error}`, + }) + } + } + + async show() { + try { + const user = await this.loadUser() + if (isNil(user)) { + return this.response.status(404).json({ + message: "User not found", + }) + } + + const policy = this.buildPolicy(user) + if (!policy.show()) { + return this.response.status(403).json({ + message: "You are not authorized to view this user", + }) + } + + return this.response.json({ user, policy }) + } catch (error) { + logger.error("Error fetching user" + error) + return this.response.status(400).json({ + message: `Error fetching user: ${error}`, + }) + } + } +} +``` diff --git a/api/src/serializers/base-serializer.ts b/api/src/serializers/base-serializer.ts new file mode 100644 index 0000000..fdbb1e7 --- /dev/null +++ b/api/src/serializers/base-serializer.ts @@ -0,0 +1,78 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +type RemainingConstructorParameters any> = C extends new ( + head: any, + ...tail: infer TT +) => any + ? TT + : [] + +/** + * BaseSerializer is a generic class that provides a common interface for all serializers. + * It is designed to be extended by other serializers, and provides a static `perform` method + * that can be used to serialize a single record or an array of records. + * + * The `perform` method is overloaded to handle both cases, and will return the serialized + * record or records based on the input. The return type is determined as the return type of the + * `perform` instance method of the subclass as a single value or an array of values. + * + * The `perform` takes its signature from the constructor of the subclass, while also allowing + * for an array of records to be passed in as the first argument. + * + * @param M The model type that the serializer is designed to handle + * + * @example + * class TableSerializer extends BaseSerializer { + * constructor( + * protected record: Dataset, + * protected currentUser: User + * ) { + * super(record) + * } + * + * perform(): DatasetTableView {} + * } + * + * TableSerializer.perform(dataset, currentUser) // => DatasetTableView + * TableSerializer.perform([dataset1, dataset2], currentUser) // => [DatasetTableView, DatasetTableView] + */ +export class BaseSerializer { + constructor(protected record: Model) {} + + // Overload for handling a single record + static perform, C extends new (...args: any[]) => T>( + this: C, + ...args: ConstructorParameters + ): ReturnType["perform"]> + + // Overload for handling an array of records + static perform, C extends new (...args: any[]) => T>( + this: C, + ...args: [ConstructorParameters[0][], ...RemainingConstructorParameters] + ): ReturnType["perform"]>[] + + // Implementation of the perform method + static perform, C extends new (...args: any[]) => T>( + this: C, + ...args: + | ConstructorParameters + | [ConstructorParameters[0][], ...RemainingConstructorParameters] + ): ReturnType["perform"]> | ReturnType["perform"]>[] { + if (Array.isArray(args[0])) { + const records = args[0] as ConstructorParameters[0][] + return records.map((record) => { + const instance = new this(record, ...args.slice(1)) + return instance.perform() + }) as ReturnType["perform"]>[] + } else { + const instance = new this(...args) + return instance.perform() as ReturnType["perform"]> + } + } + + perform(): any { + throw new Error("Not Implemented") + } +} + +export default BaseSerializer diff --git a/api/src/serializers/current-user/index.ts b/api/src/serializers/current-user/index.ts new file mode 100644 index 0000000..d3f3c0b --- /dev/null +++ b/api/src/serializers/current-user/index.ts @@ -0,0 +1 @@ +export { ShowSerializer } from "./show-serializer" diff --git a/api/src/serializers/current-user/show-serializer.ts b/api/src/serializers/current-user/show-serializer.ts new file mode 100644 index 0000000..5ab9ac3 --- /dev/null +++ b/api/src/serializers/current-user/show-serializer.ts @@ -0,0 +1,28 @@ +import { pick } from "lodash" + +import { User } from "@/models" +import BaseSerializer from "@/serializers/base-serializer" + +export type UserShowView = Pick< + User, + "id" | "email" | "firstName" | "lastName" | "displayName" | "roles" | "createdAt" | "updatedAt" +> + +export class ShowSerializer extends BaseSerializer { + perform(): UserShowView { + return { + ...pick(this.record, [ + "id", + "email", + "firstName", + "lastName", + "displayName", + "roles", + "createdAt", + "updatedAt", + ]), + } + } +} + +export default ShowSerializer diff --git a/api/src/serializers/index.ts b/api/src/serializers/index.ts new file mode 100644 index 0000000..f5381be --- /dev/null +++ b/api/src/serializers/index.ts @@ -0,0 +1,2 @@ +// Bundled exports +export * as Users from "./users" diff --git a/api/src/serializers/users/index-serializer.ts b/api/src/serializers/users/index-serializer.ts new file mode 100644 index 0000000..f9a9781 --- /dev/null +++ b/api/src/serializers/users/index-serializer.ts @@ -0,0 +1,19 @@ +import { pick } from "lodash" + +import { User } from "@/models" +import BaseSerializer from "@/serializers/base-serializer" + +export type UserIndexView = Pick< + User, + "id" | "email" | "firstName" | "lastName" | "displayName" | "roles" +> + +export class IndexSerializer extends BaseSerializer { + perform(): UserIndexView { + return { + ...pick(this.record, ["id", "email", "firstName", "lastName", "displayName", "roles"]), + } + } +} + +export default IndexSerializer diff --git a/api/src/serializers/users/index.ts b/api/src/serializers/users/index.ts new file mode 100644 index 0000000..0e5fcf6 --- /dev/null +++ b/api/src/serializers/users/index.ts @@ -0,0 +1,2 @@ +export { ShowSerializer } from "./show-serializer" +export { IndexSerializer } from "./index-serializer" diff --git a/api/src/serializers/users/show-serializer.ts b/api/src/serializers/users/show-serializer.ts new file mode 100644 index 0000000..5ab9ac3 --- /dev/null +++ b/api/src/serializers/users/show-serializer.ts @@ -0,0 +1,28 @@ +import { pick } from "lodash" + +import { User } from "@/models" +import BaseSerializer from "@/serializers/base-serializer" + +export type UserShowView = Pick< + User, + "id" | "email" | "firstName" | "lastName" | "displayName" | "roles" | "createdAt" | "updatedAt" +> + +export class ShowSerializer extends BaseSerializer { + perform(): UserShowView { + return { + ...pick(this.record, [ + "id", + "email", + "firstName", + "lastName", + "displayName", + "roles", + "createdAt", + "updatedAt", + ]), + } + } +} + +export default ShowSerializer diff --git a/api/src/server.ts b/api/src/server.ts new file mode 100644 index 0000000..4b2e9d4 --- /dev/null +++ b/api/src/server.ts @@ -0,0 +1,9 @@ +import { API_PORT, APPLICATION_NAME } from "@/config" +import logger from "@/utils/logger" +import app from "@/app" +import { enqueueJobs } from "@/scheduler" + +app.listen(API_PORT, async () => { + logger.info(`${APPLICATION_NAME} API listenting on port ${API_PORT}`) + await enqueueJobs() +}) diff --git a/api/src/services/base-service.ts b/api/src/services/base-service.ts new file mode 100644 index 0000000..eff4419 --- /dev/null +++ b/api/src/services/base-service.ts @@ -0,0 +1,53 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type HasNoArgsConstructor = T extends { new (): any } ? true : false + +type CleanConstructorParameters = + HasNoArgsConstructor extends true ? [] : ConstructorParameters + +export class BaseService { + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + constructor(...args: any[]) {} + + static perform( + this: T, + ...args: CleanConstructorParameters + ): ReturnType["perform"]> { + const instance = new this(...args) + return instance.perform() + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + perform(): any { + throw new Error("Not Implemented") + } +} + +export default BaseService + +// Type Testing - keeping until I have real tests implemented +// class AsyncService extends BaseService { +// private param1: number + +// constructor(param1: number) { +// super() +// this.param1 = param1 +// } + +// async perform(): Promise { +// return ["async-string1", "async-string2"] +// } +// } + +// class NonAsyncService extends BaseService { +// perform(): string { +// return "non-async-string" +// } +// } + +// const param1 = 77 +// AsyncService.perform(param1).then((result: string[]) => { +// logger.log(result) +// }) + +// const result = NonAsyncService.perform() +// logger.log(result) diff --git a/api/src/services/index.ts b/api/src/services/index.ts new file mode 100644 index 0000000..f769ed3 --- /dev/null +++ b/api/src/services/index.ts @@ -0,0 +1 @@ +export * as Users from "./users" diff --git a/api/src/services/users/create-service.ts b/api/src/services/users/create-service.ts new file mode 100644 index 0000000..4e59c3d --- /dev/null +++ b/api/src/services/users/create-service.ts @@ -0,0 +1,53 @@ +import { CreationAttributes } from "@sequelize/core" +import { isNil, random } from "lodash" + +import { User } from "@/models" +import BaseService from "@/services/base-service" + +export type UserCreationAttributes = Partial> + +export class CreateService extends BaseService { + constructor(private attributes: UserCreationAttributes) { + super() + } + + async perform(): Promise { + const { email, auth0Subject, roles, ...optionalAttributes } = this.attributes + + if (isNil(email)) { + throw new Error("Email is required") + } + + if (isNil(auth0Subject)) { + throw new Error("Auth0 Subject is required") + } + + const [emailLocalPart] = email.split("@") + /** + * Yep, if we don't have enough data, your name becomes your email split randomly. + * This way we can at least have a first name and last name, + * and the first and last name are likely to be distinct. + */ + const randomSplit = random(1, emailLocalPart.length - 2) + const [firstNameFallback, lastNameFallback] = emailLocalPart.includes(".") + ? emailLocalPart.split(".") + : [emailLocalPart.slice(0, randomSplit), emailLocalPart.slice(randomSplit)] + const { firstName, lastName } = optionalAttributes + const firstNameOrFallback = firstName || firstNameFallback + const lastNameOrFallback = lastName || lastNameFallback + + const user = await User.create({ + ...optionalAttributes, + email, + auth0Subject: auth0Subject, + firstName: firstNameOrFallback, + lastName: lastNameOrFallback, + displayName: `${firstNameOrFallback} ${lastNameOrFallback}`, + roles: roles ?? [User.Roles.USER], + }) + + return user + } +} + +export default CreateService diff --git a/api/src/services/users/destroy-service.ts b/api/src/services/users/destroy-service.ts new file mode 100644 index 0000000..8315bb4 --- /dev/null +++ b/api/src/services/users/destroy-service.ts @@ -0,0 +1,14 @@ +import { User } from "@/models" +import BaseService from "@/services/base-service" + +export class DestroyService extends BaseService { + constructor(private user: User) { + super() + } + + async perform() { + throw new Error("Not implemented") + } +} + +export default DestroyService diff --git a/api/src/services/users/ensure-from-auth0-token-service.ts b/api/src/services/users/ensure-from-auth0-token-service.ts new file mode 100644 index 0000000..773c05b --- /dev/null +++ b/api/src/services/users/ensure-from-auth0-token-service.ts @@ -0,0 +1,50 @@ +import { auth0Integration } from "@/integrations" +import { User } from "@/models" +import { Op } from "@sequelize/core" +import BaseService from "@/services/base-service" +import { Users } from "@/services" + +export class EnsureFromAuth0TokenService extends BaseService { + constructor(private token: string) { + super() + } + + async perform(): Promise { + const { auth0Subject, email, firstName, lastName } = await auth0Integration.getUserInfo( + this.token + ) + + const existingUser = await User.withScope(["asCurrentUser"]).findOne({ + where: { auth0Subject }, + }) + + if (existingUser) { + return existingUser + } + + const firstTimeUser = await User.withScope(["asCurrentUser"]).findOne({ + where: { [Op.or]: [{ auth0Subject: email }, { email: email }] }, + }) + + if (firstTimeUser) { + await firstTimeUser.update({ auth0Subject }) + return firstTimeUser + } + + await Users.CreateService.perform({ + auth0Subject, + email, + firstName, + lastName, + }) + + const newUser = await User.withScope(["asCurrentUser"]).findOne({ + where: { auth0Subject }, + rejectOnEmpty: true, + }) + + return newUser + } +} + +export default EnsureFromAuth0TokenService diff --git a/api/src/services/users/index.ts b/api/src/services/users/index.ts new file mode 100644 index 0000000..edeffe5 --- /dev/null +++ b/api/src/services/users/index.ts @@ -0,0 +1,6 @@ +export { CreateService } from "./create-service" +export { UpdateService } from "./update-service" +export { DestroyService } from "./destroy-service" + +// Special Services +export { EnsureFromAuth0TokenService } from "./ensure-from-auth0-token-service" diff --git a/api/src/services/users/update-service.ts b/api/src/services/users/update-service.ts new file mode 100644 index 0000000..b97588b --- /dev/null +++ b/api/src/services/users/update-service.ts @@ -0,0 +1,21 @@ +import { Attributes } from "@sequelize/core" + +import { User } from "@/models" +import BaseService from "@/services/base-service" + +export type UserUpdateAttributes = Partial> + +export class UpdateService extends BaseService { + constructor( + private user: User, + private attributes: UserUpdateAttributes + ) { + super() + } + + async perform(): Promise { + return this.user.update(this.attributes) + } +} + +export default UpdateService diff --git a/api/src/utils/acronymize.ts b/api/src/utils/acronymize.ts new file mode 100644 index 0000000..d836358 --- /dev/null +++ b/api/src/utils/acronymize.ts @@ -0,0 +1,14 @@ +export function acronymize(name: string) { + return name + .trim() + .split(/[\s-]+/g) + .filter((word) => word[0] === word[0].toUpperCase()) + .map((word) => { + if (!isNaN(parseInt(word[0]))) return word + + return word[0] + }) + .join("") +} + +export default acronymize diff --git a/api/src/utils/array-wrap.ts b/api/src/utils/array-wrap.ts new file mode 100644 index 0000000..f9cd59d --- /dev/null +++ b/api/src/utils/array-wrap.ts @@ -0,0 +1,11 @@ +type AsArray = T extends [] ? T : T[] + +/** + * Wraps its argument in an array unless it is already an array (or array-like). + * See https://api.rubyonrails.org/classes/Array.html#method-c-wrap + */ +export function arrayWrap(value: T | T[]): AsArray { + return Array.isArray(value) ? (value as AsArray) : ([value] as AsArray) +} + +export default arrayWrap diff --git a/api/src/utils/base64-to-buffer.ts b/api/src/utils/base64-to-buffer.ts new file mode 100644 index 0000000..4c094eb --- /dev/null +++ b/api/src/utils/base64-to-buffer.ts @@ -0,0 +1,24 @@ +/** + * Converts a base64 data URL to a Buffer + * @param dataUrl - Base64 data URL string (e.g., "data:image/png;base64,iVBORw0KGgo...") + * @returns Buffer containing the binary data, or null if input is null/undefined + */ +export function base64ToBuffer(dataUrl: string | null | undefined): Buffer | null { + if (!dataUrl) { + return null + } + + // Extract the base64 data from the data URL + // Format: data:image/png;base64, + const base64Match = dataUrl.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/) + + if (!base64Match) { + // If it's not a data URL, assume it's already base64 encoded + return Buffer.from(dataUrl, "base64") + } + + const base64Data = base64Match[2] + return Buffer.from(base64Data, "base64") +} + +export default base64ToBuffer diff --git a/api/src/utils/chunk-string.ts b/api/src/utils/chunk-string.ts new file mode 100644 index 0000000..5467901 --- /dev/null +++ b/api/src/utils/chunk-string.ts @@ -0,0 +1,23 @@ +/** + * Splits a string into chunks of a given size. + * Prefers first chunk to be the smallest, if string cannot be split evenly. + * + * e.g. + * chunkString("1234567890", 4) => ["12", "3456", "7890"] + */ +export function chunkString(string: string, chunkSize: number = 4): string[] { + const result = [] + let currentIndex = string.length + + // Loop from the end of the string and slice groups of chunkSize + while (currentIndex > 0) { + const start = Math.max(currentIndex - chunkSize, 0) + const chunk = string.slice(start, currentIndex) + result.unshift(chunk) + currentIndex -= chunkSize + } + + return result +} + +export default chunkString diff --git a/api/src/utils/coerce-empty-strings-to-null.ts b/api/src/utils/coerce-empty-strings-to-null.ts new file mode 100644 index 0000000..b0036cf --- /dev/null +++ b/api/src/utils/coerce-empty-strings-to-null.ts @@ -0,0 +1,18 @@ +/** + * Converts empty string values to null in an attributes object. + * + * When HTML form inputs are cleared, they send "" (empty string) which + * Sequelize rejects for numeric/decimal columns. This utility coerces + * those empty strings to null before the attributes reach the model. + */ +export function coerceEmptyStringsToNull>(attributes: T): T { + const result: Record = { ...attributes } + for (const key of Object.keys(result)) { + if (result[key] === "") { + result[key] = null + } + } + return result as T +} + +export default coerceEmptyStringsToNull diff --git a/api/src/utils/compact-sql.ts b/api/src/utils/compact-sql.ts new file mode 100644 index 0000000..b342157 --- /dev/null +++ b/api/src/utils/compact-sql.ts @@ -0,0 +1,17 @@ +/** + * Don't overuse this, it's not a full SQL parser. + * It's only purpose is to make SQL formatted by Sequelize 6 a bit more readable during development. + */ +export function compactSql(sql: string) { + const multiLineCommentPattern = /\/\*[\s\S]*?\*\//g + const singleLineCommentPattern = /--.*$/gm + const multiWhitespacePattern = /\s+/g + + return sql + .replace(multiLineCommentPattern, "") + .replace(singleLineCommentPattern, "") + .replace(multiWhitespacePattern, " ") + .trim() +} + +export default compactSql diff --git a/api/src/utils/db-error-helpers.ts b/api/src/utils/db-error-helpers.ts new file mode 100644 index 0000000..a59bb37 --- /dev/null +++ b/api/src/utils/db-error-helpers.ts @@ -0,0 +1,25 @@ +import { has } from "lodash" + +export function isCredentialFailure(error: unknown) { + return ( + error instanceof Error && + ((has(error, "code") && error.code === "ELOGIN") || + error.message.includes("Login failed for user")) + ) +} + +export function isSocketFailure(error: unknown) { + return error instanceof Error && has(error, "code") && error.code === "ESOCKET" +} + +export function isMissingDatabaseFailure(error: unknown) { + return error instanceof Error && has(error, "code") && error.code === "3D000" +} + +export function isNetworkFailure(error: unknown) { + return ( + error instanceof Error && + ((has(error, "code") && error.code === "EAI_AGAIN") || + error.message.includes("getaddrinfo EAI_AGAIN")) + ) +} diff --git a/api/src/utils/deep-pick.ts b/api/src/utils/deep-pick.ts new file mode 100644 index 0000000..2374e7c --- /dev/null +++ b/api/src/utils/deep-pick.ts @@ -0,0 +1,102 @@ +import { + cloneDeep, + isArray, + isBoolean, + isNull, + isNumber, + isObject, + isString, + isUndefined, +} from "lodash" + +export type Path = + | string + | { + [key: string]: (string | Path)[] + } + +/* +Usage: +const object = { + a: 1, + b: 2, + c: { + d: 4, + f: 5, + }, + g: [ + { + h: 6, + i: 7, + }, + { + h: 8, + i: 9, + } + ], +} + +const picked = deepPick(object, ["a", { c: ["d"] }, { g: ["h"] }]); +console.log(picked); // Output: { a: 1, c: { d: 4 }, g: [{ h: 6 }, { h: 8 }] } + +TODO: figure out how to do this without "any" +*/ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function deepPick(object: any, paths: Path[]): any { + if (isArray(object)) { + return object.map((item) => deepPick(item, paths)) + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return paths.reduce((result: any, path: Path) => { + if (isString(path)) { + if (path in object === false) return result + + const value = cloneDeep(object[path]) + if (isSimpleType(value)) { + result[path] = value + return result + } else if (isArray(value) && value.every(isSimpleType)) { + result[path] = value + return result + } else if (isArray(value) && value.every(isObject)) { + result[path] = [] + return result + } else if (isObject(value)) { + result[path] = value + return result + } else { + throw new Error(`Unsupported value type at path: ${path} -> ${JSON.stringify(value)}`) + } + } else if (isObject(path)) { + Object.entries(path).forEach(([path, nestedPaths]) => { + if (path in object === false) return + + const value = cloneDeep(object[path]) + if (isSimpleType(value)) { + result[path] = value + } else if (isArray(value) && value.every(isSimpleType)) { + result[path] = value + } else if (Array.isArray(value) && value.every(isObject)) { + result[path] = value.map((item) => deepPick(item, nestedPaths)) + } else if (isObject(value)) { + result[path] = deepPick(value, nestedPaths) + } else { + throw new Error( + `Unsupported value structure at path: ${path} -> ${JSON.stringify(value)}` + ) + } + }) + + return result + } else { + throw new Error(`Unsupported path type: ${path}`) + } + }, {}) +} + +function isSimpleType(value: unknown) { + return ( + isString(value) || isNumber(value) || isBoolean(value) || isNull(value) || isUndefined(value) + ) +} diff --git a/api/src/utils/determine-fiscal-year.ts b/api/src/utils/determine-fiscal-year.ts new file mode 100644 index 0000000..04ed004 --- /dev/null +++ b/api/src/utils/determine-fiscal-year.ts @@ -0,0 +1,16 @@ +import { DateTime } from "luxon" + +export function determineFiscalYear() { + const today = DateTime.local() // Get the current date + const fiscalYearStartMonth = 4 // Fiscal year starts in April + + // If today's month is April or later, the fiscal year started this calendar year + if (today.month >= fiscalYearStartMonth) { + return today.year // Fiscal year is the current year + } else { + // If the month is before April, the fiscal year started last calendar year + return today.year - 1 + } +} + +export default determineFiscalYear diff --git a/api/src/utils/enhanced-qs-decoder.ts b/api/src/utils/enhanced-qs-decoder.ts new file mode 100644 index 0000000..fb634f1 --- /dev/null +++ b/api/src/utils/enhanced-qs-decoder.ts @@ -0,0 +1,17 @@ +import qs from "qs" + +export function enhancedQsDecoder(params: string) { + return qs.parse(params, { + strictNullHandling: true, + decoder(str, defaultDecoder, charset, type) { + if (type === "value") { + if (str === "true") return true + if (str === "false") return false + } + + return defaultDecoder(str, defaultDecoder, charset) + }, + }) +} + +export default enhancedQsDecoder diff --git a/api/src/utils/logger.ts b/api/src/utils/logger.ts new file mode 100644 index 0000000..a9ba773 --- /dev/null +++ b/api/src/utils/logger.ts @@ -0,0 +1,13 @@ +import { createLogger, format, transports } from "winston" + +import { DEFAULT_LOG_LEVEL,} from "@/config" + +export const consoleLogger = createLogger({ + level: DEFAULT_LOG_LEVEL, + format: format.combine(format.colorize(), format.simple()), + transports: [new transports.Console()], +}) + +export const logger = consoleLogger + +export default logger diff --git a/api/src/utils/model-diff.ts b/api/src/utils/model-diff.ts new file mode 100644 index 0000000..725e94b --- /dev/null +++ b/api/src/utils/model-diff.ts @@ -0,0 +1,86 @@ +import { isArray, isNil } from "lodash" +import { DateTime } from "luxon" + +export function generateDiff( + oldAttributes: T, + newAttributes: Partial, + auditiableAttributes: Partial +): string { + const diff = new Array() + + Object.keys(auditiableAttributes).forEach((key) => { + const oldValue = oldAttributes[key as keyof T] + const newValue = newAttributes[key as keyof T] + + if (isArray(oldValue) || isArray(newValue)) { + const oVArray = (oldValue as unknown[]).join(", ") + const nVArray = (newValue as unknown[]).join(", ") + if (oVArray !== nVArray) { + diff.push(`${key}: '${oVArray}' => '${nVArray}'`) + } + } else if (!isNil(isDateTime(oldValue, newValue))) { + const res = isDateTime(oldValue, newValue) + if (res?.v1 !== res?.v2) { + diff.push(`${key}: '${res?.v1}' => '${res?.v2}'`) + } + } else if (typeof oldValue === "string" || typeof newValue === "string") { + if (oldValue !== newValue) { + diff.push(`${key}: '${oldValue}' => '${newValue}'`) + } + } else if (typeof oldValue === "number" || typeof newValue === "number") { + if (oldValue !== newValue) { + diff.push(`${key}: '${oldValue}' => '${newValue}'`) + } + } else if (oldValue !== newValue) { + diff.push(`${key}: '${oldValue}' => '${newValue}'`) + } + }) + + if (diff.length === 0) return "No changes detected" + + return diff.join("\n") +} + +function isDateTime( + value1: unknown, + value2: unknown +): { v1: string | null; v2: string | null } | null { + let v1 = null as string | null + let v2 = null as string | null + + if (typeof value1 === "undefined" || value1 === null) return null + + try { + if (typeof value1 == "string") { + const v1Valid = DateTime.fromISO(value1).isValid + + if (v1Valid) v1 = DateTime.fromISO(value1).toUTC().toISO() + } + if (typeof value1 == "object") { + const v1Valid = DateTime.fromJSDate(value1 as Date).isValid + if (v1Valid) + v1 = DateTime.fromJSDate(value1 as Date) + .toUTC() + .toISO() + } + + if (typeof value2 == "string") { + const v1Valid = DateTime.fromISO(value2).isValid + + if (v1Valid) v2 = DateTime.fromISO(value2).toUTC().toISO() + } + if (typeof value2 == "object") { + const v1Valid = DateTime.fromJSDate(value2 as Date).isValid + if (v1Valid) + v2 = DateTime.fromJSDate(value2 as Date) + .toUTC() + .toISO() + } + + if (!isNil(v1) || !isNil(v2)) return { v1, v2 } + + return null + } catch (_error) { + return null + } +} diff --git a/api/src/utils/search-fields-by-terms-factory.ts b/api/src/utils/search-fields-by-terms-factory.ts new file mode 100644 index 0000000..8e6157b --- /dev/null +++ b/api/src/utils/search-fields-by-terms-factory.ts @@ -0,0 +1,52 @@ +import { + AttributeNames, + Attributes, + FindOptions, + Model, + Op, + WhereOptions, + sql, + where, +} from "@sequelize/core" + +import arrayWrap from "@/utils/array-wrap" + +/** + * Generates a search scope for Sequelize models that allows for custom SQL conditions per term. + */ +export function searchFieldsByTermsFactory( + fields: AttributeNames[] +): (termOrTerms: string | string[]) => FindOptions> { + return (termOrTerms: string | string[]): FindOptions> => { + const terms = arrayWrap(termOrTerms) + if (terms.length === 0) { + return {} + } + + // TODO: rebuild as successive scope calls once + // https://github.com/sequelize/sequelize/issues/17304 is fixed + // (we would no longer need the and operator in the where clause) + const whereQuery: { + [Op.and]?: WhereOptions[] + } = {} + + const whereConditions: WhereOptions[] = terms.map((term: string) => { + const termPattern = `%${term.toLowerCase()}%` + const fieldsQuery = fields.map((field) => { + return where(sql.fn("LOWER", sql.attribute(field)), Op.like, termPattern) + }) + + return { + [Op.or]: fieldsQuery, + } + }) + + whereQuery[Op.and] = whereConditions + + return { + where: whereQuery, + } + } +} + +export default searchFieldsByTermsFactory diff --git a/api/src/utils/sleep.ts b/api/src/utils/sleep.ts new file mode 100644 index 0000000..6b5a319 --- /dev/null +++ b/api/src/utils/sleep.ts @@ -0,0 +1,5 @@ +export function sleep(seconds: number) { + return new Promise((resolve) => setTimeout(resolve, seconds * 1000)) +} + +export default sleep diff --git a/api/src/utils/strip-trailing-slash.ts b/api/src/utils/strip-trailing-slash.ts new file mode 100644 index 0000000..94279ee --- /dev/null +++ b/api/src/utils/strip-trailing-slash.ts @@ -0,0 +1,3 @@ +export function stripTrailingSlash(url: string) { + return url.endsWith("/") ? url.slice(0, -1) : url +} diff --git a/api/src/utils/to-sentence.ts b/api/src/utils/to-sentence.ts new file mode 100644 index 0000000..66f5caf --- /dev/null +++ b/api/src/utils/to-sentence.ts @@ -0,0 +1,13 @@ +import { last } from "lodash" + +export function toSentence(items: string[]): string { + if (items.length === 0) return "" + if (items.length === 1) return items[0] + if (items.length === 2) return items.join(" and ") + + const itemsExceptLast = items.slice(0, -1).join(", ") + const lastItem = last(items) + return `${itemsExceptLast}, and ${lastItem}` +} + +export default toSentence diff --git a/api/src/utils/with-logging-factory.ts b/api/src/utils/with-logging-factory.ts new file mode 100644 index 0000000..8e48052 --- /dev/null +++ b/api/src/utils/with-logging-factory.ts @@ -0,0 +1,61 @@ +import logger from "@/utils/logger" + +/** + * Wraps an async function with logging for start, completion, and errors. + * Accepts positional parameters like findEach. + */ +function withLoggingFactory( + description: string, + wrappedFunction: () => Promise +): () => Promise + +function withLoggingFactory>( + description: string, + context: T, + wrappedFunction: (context: T) => Promise +): () => Promise + +function withLoggingFactory>( + description: string, + contextOrFunction?: T | (() => Promise), + wrappedFunction?: (context: T) => Promise +): () => Promise { + // 2-argument version: (description, wrappedFunction) + if (typeof contextOrFunction === "function") { + return async () => { + logger.info(`Starting: ${description}`) + + try { + await contextOrFunction() + logger.info(`Completed: ${description}`) + } catch (error) { + logger.error(`Failed: ${description}`, { error }) + throw error + } + } + } + + // 3-argument version: (description, context, wrappedFunction) + if (typeof contextOrFunction !== "object") { + throw new Error("Missing context") + } + const context: T = contextOrFunction + + if (typeof wrappedFunction !== "function") { + throw new Error("Missing wrapped function") + } + + return async () => { + logger.info(`Starting: ${description} with ${context}`, { context }) + + try { + await wrappedFunction(context) + logger.info(`Completed: ${description} with ${context}`, { context }) + } catch (error) { + logger.error(`Failed: ${description} with ${context}`, { context, error }) + throw error + } + } +} + +export default withLoggingFactory diff --git a/api/src/web/404.html b/api/src/web/404.html new file mode 100644 index 0000000..cd85810 --- /dev/null +++ b/api/src/web/404.html @@ -0,0 +1,29 @@ + + + + + + 404 Not Found + + + +

Error 404

+

Oops! The page you're looking doesn't exist.

+
+

Site: ${applicationName}

+

Version: ${releaseTag}

+

Commit Hash: ${gitCommitHash}

+ + diff --git a/api/src/web/index.html b/api/src/web/index.html new file mode 100644 index 0000000..7da92c5 --- /dev/null +++ b/api/src/web/index.html @@ -0,0 +1,28 @@ + + + + + + 404 Not Found + + + +

Error 404

+

This is a stub is meant to be replaced by the compiled front-end in production

+
+

Site: Guardian

+

Version: 0.0.1

+ + diff --git a/api/tests/README.md b/api/tests/README.md new file mode 100644 index 0000000..f6ece57 --- /dev/null +++ b/api/tests/README.md @@ -0,0 +1,80 @@ +# API service Tests + +## Implementation + +Tests are written in [vitest](https://vitest.dev/guide/) + +Test initialization goes like this: + +1. `api/vitest.config.mts` loads the ts config and finds the appropriate setup functions. + +2. Before running the tests, it runs the `globalSetup` function from `api/tests/global-setup.ts`. This does things like setting up the database and running migrations and base seeds. + +3. Next it loads a specific test file triggers the `setupFiles` files, currently only `api/tests/setup.ts`. These setup files add callbacks that will run before/after _each test file_ runs, so they should be performant. Mostly cleanup functions. + +4. It runs the actual tests in the loaded file. + +5. (Currently) Runs `beforeEach` callback that cleans the database before each test file is run. + +6. Runs the next test file, and repeats from step 3. + +## General Notes About Tests + +1. Tests should map to a specific file in the api/src folder. + + e.g. + + - `api/src/models/funding-submission-line-json.ts` maps to `api/tests/models/funding-submission-line-json.test.ts` + - `api/src/services/centre-services.ts` maps to `api/tests/services/centre-services.test.ts` + +2. Tests should follow the naming convention `{filename}.test.{extension}`. +3. Test file location should be moved if a given file is moved, and deleted if the file under test is deleted. +4. A good general pattern for a test is + + ```typescript + describe("api/src/services/centre-services.ts", () => { // references file under test + describe("CentreServices", () => { // references class or model under test + describe(".create", () => { // referneces a specific method on the class or model + test("creates a new centre in the database", async () => { // descriptive message about the specific behaviour under test + }) + }) + }) + ``` + +5. I'm using a plugin that lets me switch between the test and non-test file, and creates the test file if it does not exist. It's not great, but it mostly works. See + + It requires this config (in your workspace or `.vscode/settings.json`). + + > Note that if this is in your worspace config must be inside the "settings" entry. i.e. `{ "settings": { // these settings } }`. + + ```json + { + "createTestFile.nameTemplate": "{filename}.test.{extension}", + "createTestFile.languages": { + "[vue]": { + "createTestFile.nameTemplate": "{filename}.test.{extension}.ts" + } + }, + "createTestFile.pathMaps": [ + { + // Other examples + // "pathPattern": "/?(.*)", + // "testFilePathPattern": "spec/$1" + "pathPattern": "(api)/src/?(.*)", + "testFilePathPattern": "$1/tests/$2" + }, + { + "pathPattern": "(web)/src/?(.*)", + "testFilePathPattern": "$1/tests/$2" + } + ], + "createTestFile.isTestFileMatchers": [ + "^(?:test|spec)s?/", + "/(?:test|spec)s?/", + "/?(?:test|spec)s?_", + "/?_(?:test|spec)s?", + "/?\\.(?:test|spec)s?", + "/?(?:test|spec)s?\\." + ] + } + ``` diff --git a/api/tests/controllers/current-user-controller.test.ts b/api/tests/controllers/current-user-controller.test.ts new file mode 100644 index 0000000..991617a --- /dev/null +++ b/api/tests/controllers/current-user-controller.test.ts @@ -0,0 +1,25 @@ +import { User } from "@/models" + +import { userFactory } from "@/factories" +import { mockCurrentUser, request } from "@/support" + +describe("api/src/controllers/current-user-controller.ts", () => { + describe("CurrentUserController", () => { + describe("#show", () => { + test("it returns the policy alongside the user", async () => { + // Arrange + const currentUser = await userFactory.create({ + roles: [User.Roles.SYSTEM_ADMIN], + }) + mockCurrentUser(currentUser) + + // Act + const response = await request().get("/api/current-user") + + // Assert + expect(response.status).toBe(200) + expect(response.body.policy).toBeDefined() + }) + }) + }) +}) diff --git a/api/tests/controllers/users-controller.test.ts b/api/tests/controllers/users-controller.test.ts new file mode 100644 index 0000000..690be30 --- /dev/null +++ b/api/tests/controllers/users-controller.test.ts @@ -0,0 +1,32 @@ +import { User } from "@/models" + +import { userFactory } from "@/factories" +import { mockCurrentUser, request } from "@/support" + +describe("api/src/controllers/users-controller.ts", () => { + beforeEach(async () => { + const currentUser = await userFactory.create({ + roles: [User.Roles.SYSTEM_ADMIN], + }) + mockCurrentUser(currentUser) + }) + + describe("UsersController", () => { + describe("#create", () => { + test("when creating a new user as a system admin, it creates the user", async () => { + // Arrange + const attributes = { + email: "test_create@example.com", + auth0Subject: "test_create@example.com", + } + + // Act + const response = await request().post("/api/users").send(attributes) + + // Assert + expect(response.status).toBe(201) + expect(response.body.user.email).toEqual(attributes.email) + }) + }) + }) +}) diff --git a/api/tests/factories/base-factory.ts b/api/tests/factories/base-factory.ts new file mode 100644 index 0000000..81f6efb --- /dev/null +++ b/api/tests/factories/base-factory.ts @@ -0,0 +1,15 @@ +import { BuildOptions, DeepPartial, Factory } from "fishery" +import { Model } from "@sequelize/core" + +// Must keep type signature in sync with fishery's Factory type +// See api/node_modules/fishery/dist/factory.d.ts +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export class BaseFactory extends Factory { + // See https://thoughtbot.github.io/factory_bot/ref/build-strategies.html#attributes_for + attributesFor(params?: DeepPartial, options?: BuildOptions): T { + const model = this.build(params, options) + return model.dataValues + } +} + +export default BaseFactory diff --git a/api/tests/factories/helpers/index.ts b/api/tests/factories/helpers/index.ts new file mode 100644 index 0000000..21a0320 --- /dev/null +++ b/api/tests/factories/helpers/index.ts @@ -0,0 +1 @@ +export { nestedSaveAndAssociateIfNew } from "./nested-save-and-associate-if-new" diff --git a/api/tests/factories/helpers/nested-save-and-associate-if-new.ts b/api/tests/factories/helpers/nested-save-and-associate-if-new.ts new file mode 100644 index 0000000..8095f18 --- /dev/null +++ b/api/tests/factories/helpers/nested-save-and-associate-if-new.ts @@ -0,0 +1,162 @@ +import { Model } from "@sequelize/core" + +/** + * Recursively saves a Sequelize model instance along with its associated models if they are new (unsaved). + * + * It handles `BelongsTo` associations and ensures associated model is saved _before_ the current model, + * then propagates the foreign key relationship before saving the current model. + * + * It handles `HasMany` associations and ensures associated models are saved _after_ the current model, + * propagating the foreign key relationship before saving. + * + * It handles `HasOne` associations and ensures the associated model is saved _after_ the current model, + * propagating the foreign key relationship before saving. + * + * @template M - A Sequelize model type extending `Model`. + * @param {M} modelInstance - The Sequelize model instance to be saved along with its associated models. + * @returns {Promise} - Returns the saved model instance. + * + * @example + * // Given a non-persisted TravelSegment model instance, being passed via a HasMany association to a TravelAuthorization model. + * // The TravelAuthorization is saved first, then the TravelSegment is updated with the travelAuthorizationId before it is saved. + * + * const travelSegment = travelSegmentFactory.build({ + * departureOn: faker.date.past(), + * }) + * const travelAuthorization = await travelAuthorizationFactory + * .associations({ + * travelSegments: [travelSegment], + * }) + * .transient({ + * include: ["user", "travelSegments"], + * }) + * .create({ + * status: TravelAuthorization.Statuses.SUBMITTED, + * }) + * + * @example + * // Given a persisted TravelAuthorization passed to a TravelSegment model instance via a BelongsTo association. + * // The TravelSegment is updated with the travelAuthorizationId before it is saved. + * const travelAuthorization = await travelAuthorizationFactory + * .transient({ roundTrip: true }) + * .create() + * const whitehorse = await locationFactory.create({ city: "Whitehorse", province: "YT" }) + * const vancouver = await locationFactory.create({ city: "Vancouver", province: "BC" }) + * const travelSegment1 = await travelSegmentFactory + * .associations({ + * travelAuthorization, + * departureLocation: whitehorse, + * arrivalLocation: vancouver, + * }) + * .create({ + * segmentNumber: 1, + * departureOn: new Date("2022-06-05"), + * departureTime: Stop.BEGINNING_OF_DAY, + * modeOfTransport: Stop.TravelMethods.AIRCRAFT, + * accommodationType: Stop.AccommodationTypes.HOTEL, + * }) + * + * @example + * // Given a generic TravelAuthorization creation. + * // The factory will create the purpose and user associations if they are not provided. + * // Then the onCreated hook will call nestedSaveAndAssociateIfNew to save + * // the purpose and user associations as they are new. + * // Then those associations will propagate their foreign keys to the travelAuthorization before it is saved. + * const travelAuthorization = await travelAuthorizationFactory.create() + */ +export async function nestedSaveAndAssociateIfNew(modelInstance: M): Promise { + const modelClass = modelInstance.constructor as typeof Model + const associations = modelClass.associations + + const belongsToAssociationNames: string[] = [] + const hasManyAssociationNames: string[] = [] + const hasOneAssociationNames: string[] = [] + + for (const [associationName, { associationType }] of Object.entries(associations)) { + if (associationType === "BelongsTo") { + belongsToAssociationNames.push(associationName) + } else if (associationType === "HasMany") { + hasManyAssociationNames.push(associationName) + } else if (associationType === "HasOne") { + hasOneAssociationNames.push(associationName) + } else { + // no-op - maybe I should warn? + } + } + + for (const associationName of belongsToAssociationNames) { + const associationDefinition = associations[associationName] + const foreignKeyName = associationDefinition.foreignKey + let foreignKeyValue = modelInstance.get(foreignKeyName) + if (foreignKeyValue !== undefined) continue + + // Maybe should be associationAccessor instead of as? + const associationAlias = associationDefinition.as as keyof M + const associatedInstance = modelInstance[associationAlias] as Model | undefined + if (associatedInstance === undefined) continue + + // @ts-expect-error - TS doesn't know that targetKey is a property of associationDefinition + const { targetKey }: { targetKey: keyof typeof associatedInstance } = associationDefinition + if (associatedInstance.isNewRecord !== true) { + foreignKeyValue = associatedInstance.get(targetKey) + modelInstance.set(foreignKeyName, foreignKeyValue) + continue + } + + const updatedAssocationInstance = await nestedSaveAndAssociateIfNew(associatedInstance) + foreignKeyValue = updatedAssocationInstance.get(targetKey) + modelInstance.set(foreignKeyName, foreignKeyValue) + } + + if (modelInstance.isNewRecord === true) { + await modelInstance.save() + } + + for (const associationName of hasManyAssociationNames) { + const associationDefinition = associations[associationName] + + // Maybe should be associationAccessor instead of as? + const associationAlias = associationDefinition.as as keyof M + const associatedInstances = modelInstance[associationAlias] as Model[] | undefined + if (associatedInstances === undefined) continue + + for (const associatedInstance of associatedInstances) { + if (associatedInstance.isNewRecord !== true) continue + + // @ts-expect-error - TS doesn't know that sourceKey is a property of associationDefinition + const { + sourceKey, + foreignKey, + }: { sourceKey: keyof M; foreignKey: keyof typeof associatedInstance } = associationDefinition + const sourceKeyValue = modelInstance.get(sourceKey) + + associatedInstance.set(foreignKey, sourceKeyValue) + await nestedSaveAndAssociateIfNew(associatedInstance) + } + } + + for (const associationName of hasOneAssociationNames) { + const associationDefinition = associations[associationName] + + // Maybe should be associationAccessor instead of as? + const associationAlias = associationDefinition.as as keyof M + const associatedInstance = modelInstance[associationAlias] as Model | undefined + if (associatedInstance === undefined) continue + + if (associatedInstance.isNewRecord !== true) continue + + // @ts-expect-error - TS doesn't know that sourceKey is a property of associationDefinition + const { + sourceKey, + foreignKey, + }: { sourceKey: keyof M; foreignKey: keyof typeof associatedInstance } = associationDefinition + const sourceKeyValue = modelInstance.get(sourceKey) + + associatedInstance.set(foreignKey, sourceKeyValue) + await nestedSaveAndAssociateIfNew(associatedInstance) + } + + return modelInstance +} + +export default nestedSaveAndAssociateIfNew diff --git a/api/tests/factories/index.ts b/api/tests/factories/index.ts new file mode 100644 index 0000000..c9e2e8e --- /dev/null +++ b/api/tests/factories/index.ts @@ -0,0 +1,2 @@ +// Factories +export { userFactory } from "./user-factory" diff --git a/api/tests/factories/user-factory.ts b/api/tests/factories/user-factory.ts new file mode 100644 index 0000000..cedc35b --- /dev/null +++ b/api/tests/factories/user-factory.ts @@ -0,0 +1,32 @@ +import { Factory } from "fishery" +import { faker } from "@faker-js/faker/locale/en_CA" + +import { User } from "@/models" + +export const userFactory = Factory.define(({ sequence, params, onCreate }) => { + onCreate((user) => { + try { + return user.save() + } catch (error) { + console.error(error) + throw new Error( + `Could not create User with attributes: ${JSON.stringify(user.dataValues, null, 2)}` + ) + } + }) + + const firstName = params.firstName || `${faker.person.firstName()}-${sequence}` + const lastName = params.lastName || faker.person.lastName() + const email = params.email || faker.internet.email({ firstName, lastName }) + + return User.build({ + email, + auth0Subject: params.auth0Subject || email, + firstName, + lastName, + displayName: `${firstName} ${lastName}`, + roles: [User.Roles.USER], + }) +}) + +export default userFactory diff --git a/api/tests/global-setup.ts b/api/tests/global-setup.ts new file mode 100644 index 0000000..606035c --- /dev/null +++ b/api/tests/global-setup.ts @@ -0,0 +1,11 @@ +import { execSync } from 'child_process'; + +export default async function globalSetup() { + try { + // Keep in sync with api/bin/boot-app.sh + execSync(`npm run initializers`, { stdio: 'inherit' }); + } catch (error) { + console.error('Failed to run importAndExecuteInitializers:', error); + process.exit(1); + } +} diff --git a/api/tests/models/user.test.ts b/api/tests/models/user.test.ts new file mode 100644 index 0000000..a986dfc --- /dev/null +++ b/api/tests/models/user.test.ts @@ -0,0 +1,25 @@ +import { userFactory } from "@/factories" + +import { UserRoles } from "@/models/user" + +describe("api/src/models/user.ts", () => { + describe("User", () => { + describe("#isSystemAdmin", () => { + test("when user has system_admin role, it returns true", async () => { + // Arrange + const user = await userFactory.create({ roles: [UserRoles.SYSTEM_ADMIN] }) + + // Act & Assert + expect(user.isSystemAdmin).toBe(true) + }) + + test("when user does not have system_admin role, it returns false", async () => { + // Arrange + const user = await userFactory.create({ roles: [UserRoles.USER] }) + + // Act & Assert + expect(user.isSystemAdmin).toBe(false) + }) + }) + }) +}) diff --git a/api/tests/setup.ts b/api/tests/setup.ts new file mode 100644 index 0000000..59cb006 --- /dev/null +++ b/api/tests/setup.ts @@ -0,0 +1,29 @@ +/** + * See https://vitest.dev/config/#setupfiles + * + * Run some code before each test file. + * + * WARNING: Be very careful of imports in this file!!! + * Vitest will not mock modules that were imported inside a setup file because they are + * cached by the time a test file is running. + * You can do + * ```ts + * vi.hoisted(() => { + * vi.resetModules() + * }) + * ``` + * to clear all module caches before running a test file. + * See: https://vitest.dev/api/vi#vi-mock + */ + +// Global Mocks +import cleanDatabase from "@/support/clean-database" +import mockedAxios from "@/support/mock-axios" + +beforeEach(async () => { + await cleanDatabase() +}) + +afterEach(() => { + mockedAxios.reset() +}) diff --git a/api/tests/support/clean-database.ts b/api/tests/support/clean-database.ts new file mode 100644 index 0000000..78dd1ed --- /dev/null +++ b/api/tests/support/clean-database.ts @@ -0,0 +1,57 @@ +import { QueryTypes } from "@sequelize/core" + +import { isNil } from "lodash" + +import db from "@/db/db-client" + +async function getTableNames() { + const query = /* sql */ ` + SELECT + table_name as "tableName" + FROM + information_schema.tables + WHERE + table_schema = 'public' + AND table_type = 'BASE TABLE' + AND table_name != 'SequelizeMeta' + AND table_name != 'knex_migrations' + AND table_name != 'knex_migrations_lock'; + ` + + try { + const result = await db.query<{ tableName: string }>(query, { type: QueryTypes.SELECT }) + const tableNames = result.map((row) => row.tableName) + return tableNames + } catch (error) { + console.error("Error fetching table names:", error) + throw error + } +} + +async function buildCleanDatabaseQuery() { + const tableNames = await getTableNames() + const quotedTableNames = tableNames.map((name) => `"${name}"`) + return /* sql */ ` + TRUNCATE TABLE ${quotedTableNames.join(",\n ")} RESTART IDENTITY CASCADE; + ` +} + +let cleanDatabaseQuery: string | null = null + +export async function cleanDatabase() { + if (isNil(cleanDatabaseQuery)) { + cleanDatabaseQuery = await buildCleanDatabaseQuery() + } + + try { + // TODO: once all tables are in Sequelize models, use this instead: + // await db.truncate({ cascade: true, restartIdentity: true }) + await db.query(cleanDatabaseQuery, { raw: true }) + return true + } catch (error) { + console.error(error) + return false + } +} + +export default cleanDatabase diff --git a/api/tests/support/index.ts b/api/tests/support/index.ts new file mode 100644 index 0000000..def65d7 --- /dev/null +++ b/api/tests/support/index.ts @@ -0,0 +1,6 @@ +export { cleanDatabase } from "./clean-database" +export { loadTestData } from "./load-test-data" +export { mockCurrentUser } from "./mock-current-user" +export { mockedAxios } from "./mock-axios" +export { request } from "./request" +export { testWithCustomLogLevel } from "./test-with-custom-log-level" diff --git a/api/tests/support/load-test-data.ts b/api/tests/support/load-test-data.ts new file mode 100644 index 0000000..b3d67da --- /dev/null +++ b/api/tests/support/load-test-data.ts @@ -0,0 +1,39 @@ +import path from "path" +import { readFileSync } from "fs" + +import { APP_ROOT_PATH } from "@/config" +import arrayWrap from "@/utils/array-wrap" + +/** + * Usage: + * - `testDataPath('path/to/my-file.json')` + * - `testDataPath('path', 'to', 'my-file.json')` + */ +export function testDataPath(pathOrPathSegment: string | string[]): string { + const pathSegments = arrayWrap(pathOrPathSegment) + return path.join(APP_ROOT_PATH, "tests", "data", ...pathSegments) +} + +/** + * Usage: + * - `specData('path/to/my-file.json')` + * - `specData('path', 'to', 'my-file.json')` + * + * Returns: + * - JSON parsed object if file is a JSON file + * - Raw file content otherwise + */ +export function loadTestData(pathOrPathSegment: string | string[]): string | object { + const filePath = testDataPath(pathOrPathSegment) + const rawData = readFileSync(filePath) + + const extension = path.extname(filePath) + switch (extension) { + case ".json": + return JSON.parse(rawData.toString()) + default: + return rawData.toString() + } +} + +export default loadTestData diff --git a/api/tests/support/mock-axios.ts b/api/tests/support/mock-axios.ts new file mode 100644 index 0000000..f87d5ef --- /dev/null +++ b/api/tests/support/mock-axios.ts @@ -0,0 +1,6 @@ +import axios from "axios" +import MockAdapter from "axios-mock-adapter" + +export const mockedAxios = new MockAdapter(axios) + +export default mockedAxios diff --git a/api/tests/support/mock-current-user.ts b/api/tests/support/mock-current-user.ts new file mode 100644 index 0000000..4c502d6 --- /dev/null +++ b/api/tests/support/mock-current-user.ts @@ -0,0 +1,38 @@ +import { Request, Response, NextFunction } from "express" + +import { + authorizationMiddleware, + type AuthorizationRequest, +} from "@/middlewares/authorization-middleware" + +import { User } from "@/models" + +/** + * Usage: + * At the top level of a test file import: + * import { mockCurrentUser } from "@/support" + * + * Then where you want to set the current user: + * mockCurrentUser(currentUser) + * + * @param newCurrentUser - The user to set as the current user + */ +export function mockCurrentUser(newCurrentUser: User) { + vi.mock("@/middlewares/jwt-middleware", () => ({ + default: async (_req: Request, _res: Response, next: NextFunction) => next(), + jwtMiddleware: async (_req: Request, _res: Response, next: NextFunction) => next(), + })) + + vi.mock("@/middlewares/authorization-middleware") + const authorizationMiddlewareMock = vi.mocked(authorizationMiddleware) + authorizationMiddlewareMock.mockImplementation( + async (req: AuthorizationRequest, _res: Response, next: NextFunction) => { + const currentUser = await User.withScope(["asCurrentUser"]).findByPk(newCurrentUser.id, { + rejectOnEmpty: true, + }) + + req.currentUser = currentUser + next() + } + ) +} diff --git a/api/tests/support/request.ts b/api/tests/support/request.ts new file mode 100644 index 0000000..39d079a --- /dev/null +++ b/api/tests/support/request.ts @@ -0,0 +1,10 @@ +import supertest, { AgentOptions } from "supertest" +import { App } from "supertest/types" + +import defaultApp from "@/app" + +export function request(options?: AgentOptions | undefined, app: App = defaultApp) { + return supertest(app, options) +} + +export default request diff --git a/api/tests/support/test-with-custom-log-level.ts b/api/tests/support/test-with-custom-log-level.ts new file mode 100644 index 0000000..1f42072 --- /dev/null +++ b/api/tests/support/test-with-custom-log-level.ts @@ -0,0 +1,19 @@ +import logger from "@/utils/logger" + +function setLogLevel(level: string) { + logger.level = level +} + +export const testWithCustomLogLevel = test.extend<{ + setLogLevel: (level: string) => void +}>({ + // eslint-disable-next-line no-empty-pattern + setLogLevel: async ({}, use) => { + const originalLogLevel = logger.level + try { + await use(setLogLevel) + } finally { + setLogLevel(originalLogLevel) + } + }, +}) diff --git a/api/tests/tsconfig.json b/api/tests/tsconfig.json new file mode 100644 index 0000000..7d4dbce --- /dev/null +++ b/api/tests/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "./../tsconfig.json", + "compilerOptions": { + "baseUrl": "./../", + "paths": { + "@/*": ["./src/*", "./tests/*"] + }, + "typeRoots": ["../node_modules/@types", "../@types", "./@types"], + "types": ["node", "../vitest/globals"] + }, + "include": ["../src/**/*", "../tests/**/*", ""] +} diff --git a/api/tsconfig.json b/api/tsconfig.json new file mode 100644 index 0000000..e187d3d --- /dev/null +++ b/api/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + /* Basic Options */ + "target": "es2020" /* Specify ECMAScript target version: 'es3' (default), 'es5', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', or 'esnext'. */, + "module": "node16" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', 'esnext', or 'nodenext'. */, + "outDir": "./dist" /* Redirect output structure to the directory. */, + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + /* Module Resolution Options */ + "moduleResolution": "node16" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, + "baseUrl": "." /* Base directory to resolve non-absolute module names. */, + "paths": { + "@/*": ["./src/*", "./dist/*"] + }, + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + /* List of folders to include type definitions from. */ + // Note that when you include any type definition, you must then + // include node_modules/@types, as it is no longer included by default. + "typeRoots": ["./node_modules/@types", "./@types"], + "resolveJsonModule": true /* Supports importing .json files. */, + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "incremental": true, + "isolatedModules": true, + "useDefineForClassFields": true + }, + "include": ["src/**/*"] +} diff --git a/api/vite.config.mts b/api/vite.config.mts new file mode 100644 index 0000000..1a5d0f7 --- /dev/null +++ b/api/vite.config.mts @@ -0,0 +1,32 @@ +import { defineConfig } from "vitest/config" +import tsconfigPaths from "vite-tsconfig-paths" + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + root: ".", + projects: ["./tsconfig.json", "./tests/tsconfig.json"], + }), + ], + test: { + globals: true, + root: ".", + globalSetup: "./tests/global-setup.ts", + setupFiles: ["./tests/setup.ts"], + isolate: true, + poolOptions: { + forks: { + singleFork: true, + }, + }, + forceRerunTriggers: [ + "**/*.(html|txt)", // Rerun tests when data files change + ], + // Mocking + clearMocks: true, + mockReset: true, + restoreMocks: true, + unstubEnvs: true, + unstubGlobals: true, + }, +}) diff --git a/bin/README.md b/bin/README.md new file mode 100644 index 0000000..29e09e3 --- /dev/null +++ b/bin/README.md @@ -0,0 +1,72 @@ +# `dev` command documentation + +## `dev` Command + +The `dev` command is a helper for using docker compose. + +For example, you can run a sql script via + +```bash +dev sqlcmd -i ./data/funding_submission_lines.sql +``` + +assuming the file is located at `/db/data/funding_submission_lines.sql` + +Note that the `dev` command uses the `db` service, and so only has access to folders under the top-level `db` directory. + +## Set up `dev` command + +The `dev` command vastly simplifies development using docker compose. It only requires `ruby`; however, `direnv` and `asdf` will make it easier to use. + +It's simply a wrapper around docker compose with the ability to quickly add custom helpers. + +All commands are just strings joined together, so it's easy to add new commmands. `dev` prints out each command that it runs, so that you can run the command manually to debug it, or just so you learn some docker compose syntax as you go. + +1. (optional) Install `asdf` as seen in . + + e.g. for Linux + + ```bash + apt install curl git + + git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.12.0 + + echo ' + # asdf + . "$HOME/.asdf/asdf.sh" + . "$HOME/.asdf/completions/asdf.bash" + ' >> ~/.bashrc + ``` + +2. Install `ruby` via `asdf` as seen here , or using whatever custom Ruby install method works for your platform. + + e.g. for Linux + + ```bash + asdf plugin add ruby https://github.com/asdf-vm/asdf-ruby.git + + # install version from .tool-versions file + asdf install ruby + + asdf reshim ruby + ``` + + You will now be able to run the `./bin/dev` command. + +3. (optional) Install [direnv](https://direnv.net/) and create an `.envrc` with + + ```bash + #!/usr/bin/env bash + + PATH_add bin + ``` + + and then run `direnv allow`. + + You will now be able to do `dev xxx` instead ov `./bin/dev xxx`. + +## Extras + +If you want to take over a directory or file in Linux you can use `dev ownit `. + +If you are on Windows or Mac, and you want that to work, you should implement it in the `bin/dev` file. You might never actually need to take ownership of anything, so this might not be relevant to you. diff --git a/bin/dev b/bin/dev new file mode 100755 index 0000000..5d11696 --- /dev/null +++ b/bin/dev @@ -0,0 +1,295 @@ +#!/usr/bin/env ruby + +class DevHelper + # Support dashes in command names + COMMAND_TO_METHOD = { + "ts-node" => :ts_node, + "check-types" => :check_types, + "bash-completions" => :bash_completions, + "plantuml-to-png" => :plantuml_to_png, + } + METHOD_TO_COMMAND = COMMAND_TO_METHOD.invert + + REPLACE_PROCESS = "replace_process" + WAIT_FOR_PROCESS = "wait_for_process" + + # External Interface + def self.call(*args) + new.call(*args) + end + + # Core logic + def call(*args, **kwargs) + command = args[0] + method = COMMAND_TO_METHOD.fetch(command, command) + if args.length.positive? && respond_to?(method) + public_send(method, *args.drop(1), **kwargs) + else + compose(*args, **kwargs) + end + end + + def compose(*args, **kwargs) + command = compose_command(*args, **kwargs) + puts "Running: #{command}" unless kwargs[:slient] + + case kwargs[:execution_mode] + when WAIT_FOR_PROCESS + wait_for_process_with_logging(command) + else + exec(command) + end + end + + # Primary command wrappers + def build(*args, **kwargs) + compose(%w[build], *args, **kwargs) + end + + def compile(*args, **kwargs) + run(*%w[api npm run build], execution_mode: WAIT_FOR_PROCESS) + exit($?.exitstatus) unless $?.success? + run(*%w[web npm run build]) + end + + def up(*args, **kwargs) + compose(*%w[up --remove-orphans], *args, **kwargs) + end + + def down(*args, **kwargs) + compose(*%w[down --remove-orphans], *args, **kwargs) + end + + def logs(*args, **kwargs) + compose(*%w[logs -f], *args, **kwargs) + end + + def run(*args, **kwargs) + compose(*%w[run --rm], *args, **kwargs) + end + + def ps(*args, **kwargs) + compose(*%w[ps], *args, **kwargs) + end + + # Custom helpers + def api(*args, **kwargs) + run(*%w[api], *args, **kwargs) + end + + def web(*args, **kwargs) + run(*%w[web], *args, **kwargs) + end + + def check_types(*args, **kwargs) + run(*%w[api npm run check-types], *args, **kwargs) + end + + def test(*args, **kwargs) + service = args[0] + if service == "api" + test_api(*args.drop(1), **kwargs) + elsif service == "web" + test_web(*args.drop(1), **kwargs) + else + test_api(*args, **kwargs) + end + end + + def test_api(*args, **kwargs) + reformat_project_relative_path_filter_for_vitest!(args, "api/") + run(*%w[test_api npm run test], *args, **kwargs) + end + + def test_web(*args, **kwargs) + reformat_project_relative_path_filter_for_vitest!(args, "web/") + run(*%w[test_web npm run test], *args, **kwargs) + end + + def sqlcmd(*args, **kwargs) + db_host = ENV.fetch('DB_HOST', 'localhost') + db_user = ENV.fetch('DB_USER', 'sa') + db_pass = ENV.fetch('DB_PASS', '1m5ecure!') + db_name = ENV.fetch('DB_NAME', 'YHSI') + compose( + *%w[exec db /opt/mssql-tools/bin/sqlcmd], + *%W[-U #{db_user}], + *%W[-P #{db_pass}], + *%W[-H #{db_host}], + *%W[-d #{db_name}], + '-I', # enable quoted identifiers, e.g. "table"."column" + *args, + **kwargs + ) + end + + def db(*args, **kwargs) + compose(*%w[exec db], *args, **kwargs) + end + + def debug + api_container_id = container_id("api") + puts "Waiting for breakpoint to trigger..." + puts "'ctrl-c' to exit." + command = "docker attach --detach-keys ctrl-c #{api_container_id}" + puts "Running: #{command}" + exec(command) + exit 0 + end + + def npm(*args, **kwargs) + run(*%w[api npm], *args, **kwargs) + end + + def ts_node(*args, **kwargs) + run(*%w[api npm run ts-node], *args, **kwargs) + end + + def knex(*args, **kwargs) + if RUBY_PLATFORM =~ /linux/ + run(*%w[api npm run knex], *args, execution_mode: WAIT_FOR_PROCESS, **kwargs) + + file_or_directory = "#{project_root}/api/src/db/migrations" + exit(0) unless take_over_needed?(file_or_directory) + + ownit file_or_directory + else + run(*%w[api npm run knex], *args, **kwargs) + end + end + + def migrate(*args, **kwargs) + action = args[0] + knex("migrate:#{action}", *args.drop(1), **kwargs) + end + + def seed(*args, **kwargs) + action = args[0] + knex("seed:#{action}", *args.drop(1), **kwargs) + end + + def ownit(*args, **kwargs) + file_or_directory = args[0] + raise ScriptError, "Must provide a file or directory path." if file_or_directory.nil? + + if RUBY_PLATFORM =~ /linux/ + puts "Take ownership of the file or directory? #{file_or_directory}" + exec("sudo chown -R #{user_id}:#{group_id} #{file_or_directory}") + else + raise NotImplementedError, "Not implement for platform #{RUBY_PLATFORM}" + end + end + + def plantuml_to_png(*args, **kwargs) + file_path = args.pop + raise ScriptError, "Must provide a file path." if file_path.nil? + + png_path = file_path.gsub(/\.(wsd|pu|puml|plantuml|uml)$/, ".png") + + command = <<~BASH + curl #{args.join(" ")} \ + --data-binary @'#{file_path}' \ + http://localhost:9999/png > '#{png_path}' + BASH + + puts "Running: #{command}" + exec(command) + end + + def bash_completions + completions = + public_methods(false) + .reject { |word| %i[call].include?(word) } + .map { |word| METHOD_TO_COMMAND.fetch(word, word) } + puts completions + end + + private + + def wait_for_process_with_logging(command) + IO.popen("#{command} 2>&1") do |io| + until io.eof? + line = io.gets + puts line + end + end + end + + def container_id(container_name, *args, **kwargs) + command = compose_command(*%w[ps -q], container_name, *args, **kwargs) + puts "Running: #{command}" + id_of_container = `#{command}`.chomp + puts "Container id is: #{id_of_container}" + id_of_container + end + + def service_running?(container_name) + ps(*%w[-q --status=running], execution_mode: WAIT_FOR_PROCESS, slient: true) != "" + end + + def compose_command(*args, **kwargs) + environment = kwargs.fetch(:environment, "development") + "cd #{project_root} && docker compose -f docker-compose.#{environment}.yml #{args.join(" ")}" + end + + def project_root + @project_root ||= File.absolute_path("#{__dir__}/..") + end + + def take_over_needed?(file_or_directory) + files_owned_by_others = + system("find #{file_or_directory} -not -user #{user_id} -print -quit | grep -q .") + files_owned_by_others + end + + def user_id + unless RUBY_PLATFORM =~ /linux/ + raise NotImplementedError, "Not implement for platform #{RUBY_PLATFORM}" + end + + `id -u`.strip + end + + def group_id + unless RUBY_PLATFORM =~ /linux/ + raise NotImplementedError, "Not implement for platform #{RUBY_PLATFORM}" + end + + `id -g`.strip + end + + def reformat_project_relative_path_filter_for_vitest!(args, prefix) + if args.length.positive? && args[0].start_with?(prefix) + src_path_prefix = "#{prefix}src/" + test_path_regex = Regexp.escape(prefix) + src_path_regex = Regexp.escape(src_path_prefix) + + if args[0].start_with?(src_path_prefix) + # TODO: handle other file types + args[0] = args[0].gsub(/^#{src_path_regex}/, "tests/").gsub(/\.ts$/, ".test.ts") + else + args[0] = args[0].gsub(/^#{test_path_regex}/, "") + end + + puts "Reformatted path filter from project relative to service relative for vitest." + end + end +end + +# Only execute main function when file is executed +DevHelper.call(*ARGV) if $PROGRAM_NAME == __FILE__ + +## Dev completions +# https://iridakos.com/programming/2018/03/01/bash-programmable-completion-tutorial +# _dev_completions () { +# local dev_command_path="$(which dev)" +# local dev_function_names +# dev_function_names="$(ruby "$dev_command_path" bash_completions)" +# # COMP_WORDS: an array of all the words typed after the name of the program the compspec belongs to +# # COMP_CWORD: an index of the COMP_WORDS array pointing to the word the current cursor is at - in other words, the index of the word the cursor was when the tab key was pressed +# # COMP_LINE: the current command line +# COMPREPLY=($(compgen -W "$dev_function_names" "${COMP_WORDS[$COMP_CWORD]}")) +# } + +# complete -F _dev_completions dev +# complete -W "allow" direnv diff --git a/docker-compose.development.yml b/docker-compose.development.yml new file mode 100644 index 0000000..a579b4e --- /dev/null +++ b/docker-compose.development.yml @@ -0,0 +1,120 @@ +x-default-environment: &default-environment + TZ: "UTC" + NODE_ENV: development + DB_HOST: db + DB_USERNAME: &default-db-username alphane + DB_DATABASE: alphane_development + DB_PASSWORD: &default-db-password DevPwd99! + DB_PORT: &default-db-port 5432 + DB_TRUST_SERVER_CERTIFICATE: "true" + DB_HEALTH_CHECK_INTERVAL_SECONDS: 5 + DB_HEALTH_CHECK_TIMEOUT_SECONDS: 10 + DB_HEALTH_CHECK_RETRIES: 3 + DB_HEALTH_CHECK_START_PERIOD_SECONDS: 5 + FRONTEND_URL: "http://localhost:8080" + VITE_APPLICATION_NAME: "ALPHANE" + VITE_API_BASE_URL: "http://localhost:3000" + VITE_AUTH0_CLIENT_ID: "TRlKzdNBynpo9tU1RSmnF0p8d3IEam4J" + VITE_AUTH0_AUDIENCE: "alphane-api" + VITE_AUTH0_DOMAIN: "https://dev-7mdjzcgwirhocfwm.ca.auth0.com" + RELEASE_TAG: "${RELEASE_TAG:-development}" + GIT_COMMIT_HASH: "${GIT_COMMIT_HASH:-not-set}" + +services: + api: + build: + context: ./api + dockerfile: development.Dockerfile + image: wrap-api:latest + env_file: + - ./api/.env.development + environment: + <<: *default-environment + tty: true # allows attaching debugger, equivalent of docker exec -t + # stdin_open: true # equivalent of docker exec -i + ports: + - "3000:3000" + - "9229:9229" + volumes: + - ./api:/usr/src/api + - ./.gitignore:/usr/src/.gitignore + - ./.prettierrc.yaml:/usr/src/.prettierrc.yaml + depends_on: + - db + + web: + build: + context: ./web + dockerfile: development.Dockerfile + environment: + <<: *default-environment + ports: + - "8080:8080" + volumes: + - ./web:/usr/src/web + - ./.gitignore:/usr/src/.gitignore + - ./.prettierrc.yaml:/usr/src/.prettierrc.yaml + depends_on: + - api + + test_api: + build: + context: ./api + dockerfile: development.Dockerfile + command: /bin/true + env_file: + - ./api/.env.development + environment: + <<: *default-environment + NODE_ENV: test + DB_DATABASE: alphane_test + DEFAULT_LOG_LEVEL: "info" + DB_HEALTH_CHECK_START_PERIOD_SECONDS: 0 + tty: true + volumes: + - ./api:/usr/src/api + depends_on: + - db + profiles: + - test + + test_web: + build: + context: ./web + dockerfile: development.Dockerfile + command: /bin/true + environment: + <<: *default-environment + NODE_ENV: test + tty: true + volumes: + - ./web:/usr/src/web + profiles: + - test + + db: + image: postgres:17.6 + user: root + environment: + <<: *default-environment + POSTGRES_USER: *default-db-username + POSTGRES_PASSWORD: *default-db-password + ports: + - "5432:5432" + volumes: + - pg-data:/var/lib/postgresql/data + + # For easily generating large PlantUML diagrams + # Not relevant to production environment. + # Accessible at http://localhost:9999 + plantuml: + image: plantuml/plantuml-server:jetty + ports: + - 9999:8080 + environment: + PLANTUML_LIMIT_SIZE: 8192 + profiles: + - design + +volumes: + pg-data: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6803f0e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +services: + app: + build: . + restart: unless-stopped + env_file: + - .env + environment: + NODE_ENV: production + ports: + - "${HOST_PORT:-3000}:${HOST_PORT:-3000}" + volumes: + - ./.env:/home/node/app/.env.production + depends_on: + - db + + db: + image: postgres:17.6 + restart: always + env_file: + - .env + environment: + TZ: "UTC" + POSTGRES_USER: "${DB_USER}" + POSTGRES_PASSWORD: "${DB_PASS}" + POSTGRES_DB: "${DB_NAME}" + ports: + - "5432:5432" + volumes: + - pg-data:/var/lib/postgresql/data + +volumes: + db_data: diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b3992dc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1414 @@ +{ + "name": "alphane-development-tooling", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "alphane-development-tooling", + "devDependencies": { + "eslint": "^9.33.0", + "eslint-config-prettier": "^10.1.8", + "prettier": "^3.6.2", + "prettier-plugin-embed": "^0.5.1", + "prettier-plugin-sql": "^0.19.2" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/@types/pegjs": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/@types/pegjs/-/pegjs-0.10.6.tgz", + "integrity": "sha512-eLYXDbZWXh2uxf+w8sXS8d6KSoXTswfps6fvCUuVAGN8eRpfe7h9eSRydxiSJvo9Bf+GzifsDOr9TMQlmJdmkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "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-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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, + "license": "Python-2.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, + "license": "MIT" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "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, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "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, + "license": "BSD-2-Clause", + "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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/fast-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fast-stringify/-/fast-stringify-4.0.0.tgz", + "integrity": "sha512-lE2DIivBaLysf6hK5WH/VfMgqRbvBVHcpGVVTmA5Zi8oWIjq9YxIt6lYGdUgP1HNSXxTIat7HEIDnrSvXSeKQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsox": { + "version": "1.2.125", + "resolved": "https://registry.npmjs.org/jsox/-/jsox-1.2.125.tgz", + "integrity": "sha512-HIf1uwublnXZsy7p3yHTrhzMzrLO6xKnqXytT9pEil5QxaXi8eyer7Is4luF5hYSV4kD3v03Y32FWoAeVYTghQ==", + "dev": true, + "license": "MIT", + "bin": { + "jsox": "lib/cli.js" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/micro-memoize": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/micro-memoize/-/micro-memoize-5.1.1.tgz", + "integrity": "sha512-QDwluos8YeMijiKxZGwaV4f4tzj0soS6+xcsJhJ3+4wdEIHMyKbIKVUziebOgWX3e6yiijdoaHo+9tyhbnaWXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-equals": "^5.3.3", + "fast-stringify": "^4.0.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moo": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.3.tgz", + "integrity": "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" + }, + "bin": { + "nearley-railroad": "bin/nearley-railroad.js", + "nearley-test": "bin/nearley-test.js", + "nearley-unparse": "bin/nearley-unparse.js", + "nearleyc": "bin/nearleyc.js" + }, + "funding": { + "type": "individual", + "url": "https://nearley.js.org/#give-to-nearley" + } + }, + "node_modules/node-sql-parser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-5.4.0.tgz", + "integrity": "sha512-jVe6Z61gPcPjCElPZ6j8llB3wnqGcuQzefim1ERsqIakxnEy5JlzV7XKdO1KmacRG5TKwPc4vJTgSRQ0LfkbFw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/pegjs": "^0.10.0", + "big-integer": "^1.6.48" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/package-up/-/package-up-5.0.0.tgz", + "integrity": "sha512-MQEgDUvXCa3sGvqHg3pzHO8e9gqTCMPVrWUko3vPQGntwegmFo52mZb2abIVTjFnUcW0BcPz0D93jV5Cas1DWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-embed": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-embed/-/prettier-plugin-embed-0.5.1.tgz", + "integrity": "sha512-2Ege8gIlLNTvHElUeU5XcFsD7/dbDXkQA6H9TczHSJAGxB58nNjjifk/dlRMS5E29eqQx/z+ToA4ZVMWzjME/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "dedent": "^1.7.1", + "micro-memoize": "^5.1.1", + "package-up": "^5.0.0", + "tiny-jsonc": "^1.0.2", + "type-fest": "^5.3.1" + } + }, + "node_modules/prettier-plugin-sql": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-sql/-/prettier-plugin-sql-0.19.2.tgz", + "integrity": "sha512-DAu1Jcanpvs32OAOXsqaVXOpPs4nFLVkB3XwzRiZZVNL5/c+XdlNxWFMiMpMhYhmCG5BW3srK8mhikCOv5tPfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsox": "^1.2.123", + "node-sql-parser": "^5.3.10", + "sql-formatter": "^15.6.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + }, + "peerDependencies": { + "prettier": "^3.0.3" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sql-formatter": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.8.1.tgz", + "integrity": "sha512-nT2r90kTEYBuse9fe4r1Rp78v1mOBD35KsGc07Vo9eQSVa1TcTSnCS0zouf6BCmdzvmqBsBW+cYuBoYkHO/OWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "nearley": "^2.20.1" + }, + "bin": { + "sql-formatter": "bin/sql-formatter-cli.cjs" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tiny-jsonc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tiny-jsonc/-/tiny-jsonc-1.0.2.tgz", + "integrity": "sha512-f5QDAfLq6zIVSyCZQZhhyl0QS6MvAyTxgz4X4x3+EoCktNWEYJ6PeoEA97fyb98njpBNNi88ybpD7m+BDFXaCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", + "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3868f7e --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "alphane-development-tooling", + "private": true, + "devDependencies": { + "eslint": "^9.33.0", + "eslint-config-prettier": "^10.1.8", + "prettier": "^3.6.2", + "prettier-plugin-embed": "^0.5.1", + "prettier-plugin-sql": "^0.19.2" + } +} \ No newline at end of file diff --git a/web/.browserslistrc b/web/.browserslistrc new file mode 100644 index 0000000..dc3bc09 --- /dev/null +++ b/web/.browserslistrc @@ -0,0 +1,4 @@ +> 1% +last 2 versions +not dead +not ie 11 diff --git a/web/.eslintrc.cjs b/web/.eslintrc.cjs new file mode 100644 index 0000000..9a89770 --- /dev/null +++ b/web/.eslintrc.cjs @@ -0,0 +1,43 @@ +// https://github.com/typescript-eslint/typescript-eslint/issues/251 +module.exports = { + root: true, + env: { + browser: true, + es2021: true, + node: true, + }, + extends: [ + "eslint:recommended", + "plugin:vue/vue3-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended", + ], + overrides: [], + parser: "vue-eslint-parser", + parserOptions: { + ecmaVersion: "latest", + extraFileExtensions: [".vue"], + parser: "@typescript-eslint/parser", + tsconfigRootDir: __dirname, + project: ["./tsconfig.node.json", "./tsconfig.json", "./tests/tsconfig.json"], + sourceType: "module", + }, + plugins: ["vue", "@typescript-eslint", "prettier"], + rules: { + // Override/add rules' settings here + "vue/valid-v-slot": [ + "error", + { + allowModifiers: true, + }, + ], + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }, + ], + }, +} diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/web/.prettierrc.test.yaml b/web/.prettierrc.test.yaml new file mode 100644 index 0000000..5dfa752 --- /dev/null +++ b/web/.prettierrc.test.yaml @@ -0,0 +1,9 @@ +$schema: "https://json.schemastore.org/prettierrc" +embeddedLanguageFormatting: "auto" +trailingComma: "es5" +tabWidth: 2 +semi: false +singleQuote: false +singleAttributePerLine: true +useTabs: false +printWidth: 100 diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..ef72fd5 --- /dev/null +++ b/web/README.md @@ -0,0 +1,18 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..c43b330 --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,6003 @@ +{ + "name": "alphane-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "alphane-web", + "version": "0.1.0", + "dependencies": { + "@auth0/auth0-vue": "^2.5.0", + "@mdi/font": "^7.4.47", + "@tabler/icons-vue": "^3.36.0", + "@vueuse/core": "^13.9.0", + "@vueuse/router": "^13.9.0", + "axios": "^1.13.2", + "dompurify": "^3.3.3", + "grid-layout-plus": "^1.1.1", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "luxon": "^3.7.2", + "marked": "^17.0.5", + "md5": "^2.3.0", + "qs": "^6.14.0", + "validator": "^13.15.26", + "vue": "^3.4.21", + "vue-draggable-next": "^2.3.0", + "vue-i18n": "^11.3.0", + "vue-router": "^4.6.4", + "vue-scrollto": "^2.20.0", + "vuetify": "^3.11.4" + }, + "devDependencies": { + "@types/dompurify": "^3.0.5", + "@types/js-yaml": "^4.0.9", + "@types/lodash": "^4.17.21", + "@types/luxon": "^3.7.1", + "@types/md5": "^2.3.6", + "@types/qs": "^6.14.0", + "@types/validator": "^13.15.10", + "@typescript-eslint/eslint-plugin": "^8.50.1", + "@typescript-eslint/parser": "^8.50.1", + "@vitejs/plugin-vue": "^6.0.3", + "eslint": "^8.57.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "eslint-plugin-vue": "^9.33.0", + "jsdom": "^26.1.0", + "prettier": "^3.7.4", + "prettier-plugin-embed": "^0.5.1", + "prettier-plugin-sql": "^0.19.2", + "sass": "^1.97.1", + "typescript": "^5.9.3", + "vite": "^7.3.0", + "vite-plugin-vuetify": "^2.1.2", + "vitest": "^3.2.4", + "vue-tsc": "^3.2.1" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@auth0/auth0-auth-js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@auth0/auth0-auth-js/-/auth0-auth-js-1.9.1.tgz", + "integrity": "sha512-YY8/We7NpJyN8aDlYZI0ENRFlPsuFQ5NGETI4dkJa+BEwCibNTRlM1E9O9qc77K4TpOANZsmaADSMde2If+Alg==", + "license": "MIT", + "dependencies": { + "jose": "^6.0.8", + "openid-client": "^6.8.0" + } + }, + "node_modules/@auth0/auth0-spa-js": { + "version": "2.21.2", + "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-2.21.2.tgz", + "integrity": "sha512-CkIzlTJeae0LbYluXgNRPs7X8+Yd7W8ya/d1Cq/AGZ0d5/gfWwgDdFv9tCQR59KhRqdmvESSJUaIQjcjD0B1aA==", + "license": "MIT", + "dependencies": { + "@auth0/auth0-auth-js": "1.9.1", + "browser-tabs-lock": "1.3.0", + "dpop": "2.1.1", + "es-cookie": "1.3.2" + } + }, + "node_modules/@auth0/auth0-vue": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-vue/-/auth0-vue-2.7.0.tgz", + "integrity": "sha512-7Jw8jCNH0rQQkBP31QUKQDaMXHMcS3Qg3g7Ia6deYKe9/jDgYbMFBus/R8dcq8WZLAuI+06yTcvMzUi7w3WHNQ==", + "license": "MIT", + "dependencies": { + "@auth0/auth0-spa-js": "^2.10.0", + "vue": "^3.5.21" + }, + "peerDependencies": { + "vue-router": "^4.0.12 || ^5.0.0" + }, + "peerDependenciesMeta": { + "vue-router": { + "optional": true + } + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "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": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/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, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/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, + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@interactjs/types": { + "version": "1.10.27", + "resolved": "https://registry.npmjs.org/@interactjs/types/-/types-1.10.27.tgz", + "integrity": "sha512-BUdv0cvs4H5ODuwft2Xp4eL8Vmi3LcihK42z0Ft/FbVJZoRioBsxH+LlsBdK4tAie7PqlKGy+1oyOncu1nQ6eA==", + "license": "MIT" + }, + "node_modules/@intlify/core-base": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.6.tgz", + "integrity": "sha512-EOeHO95XESK9IFHgHeZXunsM/WBAoCA0DlaWODvx14vKmetAuS97t+l6Xe9hTUqntPpF93vtVSjjUDafw3wXMw==", + "license": "MIT", + "dependencies": { + "@intlify/devtools-types": "11.4.6", + "@intlify/message-compiler": "11.4.6", + "@intlify/shared": "11.4.6" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/devtools-types": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.6.tgz", + "integrity": "sha512-wowQPpNem56b2d43IJmqbrzG2FeBKe5f/kUGlpNuBmXs6OSqncF8m1+1lxHuW8ISZJF0ma2RkW3iLkw0g0G4VA==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.4.6", + "@intlify/shared": "11.4.6" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.6.tgz", + "integrity": "sha512-5nj3jULqeTAC1WovwMs1LQWgatTa2pM/rXN9T3XW8rdOtXW9ZF6/GLSNFTKDQmPLwclhPdgUWLJ/4w3fMeeC/Q==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "11.4.6", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.6.tgz", + "integrity": "sha512-m1p1HHAMLhqSpTRH7VnXdrN0CQ4y+9vunFkpLkbD8soIuBsnQdawZXqMCgvwI2UVF9Ww7sVaw7g9tV2VO7shoA==", + "license": "MIT", + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", + "license": "Apache-2.0" + }, + "node_modules/@mdi/font": { + "version": "7.4.47", + "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz", + "integrity": "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==", + "license": "Apache-2.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==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@pkgr/core": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tabler/icons": { + "version": "3.44.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.44.0.tgz", + "integrity": "sha512-Wn0AOZG9sg0L+bjfMqq4eNhC6pQjIrk94LvvWYNYkY8KH8wC3YILRzQlrnVJc4FUeMxH/AK97QsYCX35H3LndA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/icons-vue": { + "version": "3.44.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-vue/-/icons-vue-3.44.0.tgz", + "integrity": "sha512-mABxdhq3SWo2ZI77w/t0reiOGNim/SEDSlfMT5PeiWA3cZwnZoQUYRiq/X6SgeTaA7LzCTX0IuvQWVf4RWOvsg==", + "license": "MIT", + "dependencies": { + "@tabler/icons": "3.44.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "vue": ">=3.0.1" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-gW+Oib+vUtGJBtNC8V9Reww0oIpusw+4m81uncg9REGZAJfqOQHfo/nkabnc7w0QReXyPqjrbWMJk6NuAkiX3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/md5": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@types/md5/-/md5-2.3.6.tgz", + "integrity": "sha512-WD69gNXtRBnpknfZcb4TRQ0XJQbUPZcai/Qdhmka3sxUR3Et8NrXoeAoknG/LghYHTf4ve795rInVYHBTQdNVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/pegjs": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/@types/pegjs/-/pegjs-0.10.6.tgz", + "integrity": "sha512-eLYXDbZWXh2uxf+w8sXS8d6KSoXTswfps6fvCUuVAGN8eRpfe7h9eSRydxiSJvo9Bf+GzifsDOr9TMQlmJdmkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", + "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/type-utils": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.61.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", + "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", + "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.61.1", + "@typescript-eslint/types": "^8.61.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", + "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", + "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", + "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", + "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", + "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.61.1", + "@typescript-eslint/tsconfig-utils": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", + "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", + "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vexip-ui/hooks": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@vexip-ui/hooks/-/hooks-2.9.4.tgz", + "integrity": "sha512-dGUiBAeHIsnSVigGSPHcuHBVqrSGW8LV+zGohvOpBfXs8Ynn5ZcSmybIWJ3G826NsicPu9rqwcJG8uvSgG4k4Q==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.0", + "@juggle/resize-observer": "^3.4.0", + "@vexip-ui/utils": "2.16.4" + }, + "peerDependencies": { + "vue": "^3.2.25" + } + }, + "node_modules/@vexip-ui/utils": { + "version": "2.16.4", + "resolved": "https://registry.npmjs.org/@vexip-ui/utils/-/utils-2.16.4.tgz", + "integrity": "sha512-KX+Q4EsuwDp6ZlRJ7OAkiYxu52D5CVM8zpqQz/FXYV+JUtzl9T3dvxgtA8gQ0wm5Sh/xT6jp8Wo4X7tLAzRh/A==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", + "integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", + "integrity": "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.6.tgz", + "integrity": "sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.6.tgz", + "integrity": "sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.6.tgz", + "integrity": "sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.6", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.6.tgz", + "integrity": "sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.6.tgz", + "integrity": "sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.6.tgz", + "integrity": "sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.38.tgz", + "integrity": "sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.38", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.38.tgz", + "integrity": "sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.38", + "@vue/shared": "3.5.38" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.38.tgz", + "integrity": "sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.38", + "@vue/compiler-dom": "3.5.38", + "@vue/compiler-ssr": "3.5.38", + "@vue/shared": "3.5.38", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.38.tgz", + "integrity": "sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.38", + "@vue/shared": "3.5.38" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.5.tgz", + "integrity": "sha512-UkKu5nhX89fg4VhlG/FOeI10G3cj/7radKT/cy9BT4Q9qJmJlSTAc/dP63Xqs29aypN4f39xUV6PsLNk/dcD6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.38.tgz", + "integrity": "sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.38" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.38.tgz", + "integrity": "sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.38", + "@vue/shared": "3.5.38" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.38.tgz", + "integrity": "sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.38", + "@vue/runtime-core": "3.5.38", + "@vue/shared": "3.5.38", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.38.tgz", + "integrity": "sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.38", + "@vue/shared": "3.5.38" + }, + "peerDependencies": { + "vue": "3.5.38" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.38.tgz", + "integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==", + "license": "MIT" + }, + "node_modules/@vuetify/loader-shared": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.1.2.tgz", + "integrity": "sha512-X+1jBLmXHkpQEnC0vyOb4rtX2QSkBiFhaFXz8yhQqN2A4vQ6k2nChxN4Ol7VAY5KoqMdFoRMnmNdp/1qYXDQig==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "upath": "^2.0.1" + }, + "peerDependencies": { + "vue": "^3.0.0", + "vuetify": ">=3" + } + }, + "node_modules/@vueuse/core": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.9.0.tgz", + "integrity": "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "13.9.0", + "@vueuse/shared": "13.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.9.0.tgz", + "integrity": "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/router": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/router/-/router-13.9.0.tgz", + "integrity": "sha512-7AYay8Pv/0fC4D0eygbIyZuLyVs+9D7dsnO5D8aqat9qcOz91v/XFWR667WE1+p+OkU0ib+FjQUdnTVBNoIw8g==", + "license": "MIT", + "dependencies": { + "@vueuse/shared": "13.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0", + "vue-router": "^4.0.0" + } + }, + "node_modules/@vueuse/shared": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz", + "integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.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==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "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/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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==", + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", + "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bezier-easing": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", + "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==", + "license": "MIT" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browser-tabs-lock": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.3.0.tgz", + "integrity": "sha512-g6nHaobTiT0eMZ7jh16YpD2kcjAp+PInbiVq3M1x6KKaEIVhT4v9oURNIpZLOZ3LQbQ3XYfNhMAb/9hzNLIWrw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "lodash": ">=4.17.21" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "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, + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dompurify": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", + "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dpop": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dpop/-/dpop-2.1.1.tgz", + "integrity": "sha512-J0Of2JTiM4h5si0tlbPQ/lkqfZ5wAEVkKYBhkwyyANnPJfWH4VsR5uIkZ+T+OSPIwDYUg1fbd5Mmodd25HjY1w==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-cookie": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", + "integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@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-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.6.tgz", + "integrity": "sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.13" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz", + "integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "vue-eslint-parser": "^9.4.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "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, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "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/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "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, + "license": "BSD-2-Clause", + "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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.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, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/fast-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fast-stringify/-/fast-stringify-4.0.0.tgz", + "integrity": "sha512-lE2DIivBaLysf6hK5WH/VfMgqRbvBVHcpGVVTmA5Zi8oWIjq9YxIt6lYGdUgP1HNSXxTIat7HEIDnrSvXSeKQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "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==", + "dev": true, + "license": "MIT", + "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.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 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, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "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": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/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, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/grid-layout-plus": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grid-layout-plus/-/grid-layout-plus-1.1.1.tgz", + "integrity": "sha512-7CWehJubrVC8Ps5QFUlnDsp0kiREvKfi3Pdjp21EyY8BNzSusqI3Utcxvu1Y9UUKe3YExvbhJzIxHK6rorbRaQ==", + "license": "MIT", + "dependencies": { + "@vexip-ui/hooks": "^2.8.0", + "@vexip-ui/utils": "^2.16.1", + "interactjs": "^1.10.27" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.6.tgz", + "integrity": "sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/interactjs": { + "version": "1.10.27", + "resolved": "https://registry.npmjs.org/interactjs/-/interactjs-1.10.27.tgz", + "integrity": "sha512-y/8RcCftGAF24gSp76X2JS3XpHiUvDQyhF8i7ujemBz77hwiHDuJzftHx7thY8cxGogwGiPJ+o97kWB6eAXnsA==", + "license": "MIT", + "dependencies": { + "@interactjs/types": "1.10.27" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsox": { + "version": "1.2.125", + "resolved": "https://registry.npmjs.org/jsox/-/jsox-1.2.125.tgz", + "integrity": "sha512-HIf1uwublnXZsy7p3yHTrhzMzrLO6xKnqXytT9pEil5QxaXi8eyer7Is4luF5hYSV4kD3v03Y32FWoAeVYTghQ==", + "dev": true, + "license": "MIT", + "bin": { + "jsox": "lib/cli.js" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/marked": { + "version": "17.0.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.6.tgz", + "integrity": "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "node_modules/micro-memoize": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/micro-memoize/-/micro-memoize-5.1.1.tgz", + "integrity": "sha512-QDwluos8YeMijiKxZGwaV4f4tzj0soS6+xcsJhJ3+4wdEIHMyKbIKVUziebOgWX3e6yiijdoaHo+9tyhbnaWXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-equals": "^5.3.3", + "fast-stringify": "^4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/moo": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.3.tgz", + "integrity": "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "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==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.13.tgz", + "integrity": "sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" + }, + "bin": { + "nearley-railroad": "bin/nearley-railroad.js", + "nearley-test": "bin/nearley-test.js", + "nearley-unparse": "bin/nearley-unparse.js", + "nearleyc": "bin/nearleyc.js" + }, + "funding": { + "type": "individual", + "url": "https://nearley.js.org/#give-to-nearley" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/node-sql-parser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-5.4.0.tgz", + "integrity": "sha512-jVe6Z61gPcPjCElPZ6j8llB3wnqGcuQzefim1ERsqIakxnEy5JlzV7XKdO1KmacRG5TKwPc4vJTgSRQ0LfkbFw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/pegjs": "^0.10.0", + "big-integer": "^1.6.48" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.24", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", + "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "dev": true, + "license": "MIT" + }, + "node_modules/oauth4webapi": { + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.6.tgz", + "integrity": "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openid-client": { + "version": "6.8.4", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.4.tgz", + "integrity": "sha512-QSw0BA08piujetEwfZsHoTrDpMEha7GDZDicQqVwX4u0ChCjefvjDB++TZ8BTg76UpwhzIQgdvvfgfl3HpCSAw==", + "license": "MIT", + "dependencies": { + "jose": "^6.2.2", + "oauth4webapi": "^3.8.5" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/package-up/-/package-up-5.0.0.tgz", + "integrity": "sha512-MQEgDUvXCa3sGvqHg3pzHO8e9gqTCMPVrWUko3vPQGntwegmFo52mZb2abIVTjFnUcW0BcPz0D93jV5Cas1DWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prettier-plugin-embed": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-embed/-/prettier-plugin-embed-0.5.1.tgz", + "integrity": "sha512-2Ege8gIlLNTvHElUeU5XcFsD7/dbDXkQA6H9TczHSJAGxB58nNjjifk/dlRMS5E29eqQx/z+ToA4ZVMWzjME/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "dedent": "^1.7.1", + "micro-memoize": "^5.1.1", + "package-up": "^5.0.0", + "tiny-jsonc": "^1.0.2", + "type-fest": "^5.3.1" + } + }, + "node_modules/prettier-plugin-embed/node_modules/type-fest": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", + "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prettier-plugin-sql": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-sql/-/prettier-plugin-sql-0.19.2.tgz", + "integrity": "sha512-DAu1Jcanpvs32OAOXsqaVXOpPs4nFLVkB3XwzRiZZVNL5/c+XdlNxWFMiMpMhYhmCG5BW3srK8mhikCOv5tPfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsox": "^1.2.123", + "node-sql-parser": "^5.3.10", + "sql-formatter": "^15.6.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + }, + "peerDependencies": { + "prettier": "^3.0.3" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.101.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.0.tgz", + "integrity": "sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/sortablejs": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz", + "integrity": "sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==", + "license": "MIT", + "peer": true + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sql-formatter": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.8.1.tgz", + "integrity": "sha512-nT2r90kTEYBuse9fe4r1Rp78v1mOBD35KsGc07Vo9eQSVa1TcTSnCS0zouf6BCmdzvmqBsBW+cYuBoYkHO/OWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "nearley": "^2.20.1" + }, + "bin": { + "sql-formatter": "bin/sql-formatter-cli.cjs" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.3.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT" + }, + "node_modules/tiny-jsonc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tiny-jsonc/-/tiny-jsonc-1.0.2.tgz", + "integrity": "sha512-f5QDAfLq6zIVSyCZQZhhyl0QS6MvAyTxgz4X4x3+EoCktNWEYJ6PeoEA97fyb98njpBNNi88ybpD7m+BDFXaCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vite": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", + "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-plugin-vuetify": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vite-plugin-vuetify/-/vite-plugin-vuetify-2.1.3.tgz", + "integrity": "sha512-Q4SC/4TqbNvaZIFb9YsfBqkGlYHbJJJ6uU3CnRBZqLUF3s5eCMVZAaV4GkTbehIH/bhSj42lMXztOwc71u6rVw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@vuetify/loader-shared": "^2.1.2", + "debug": "^4.3.3", + "upath": "^2.0.1" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": ">=5", + "vue": "^3.0.0", + "vuetify": ">=3" + } + }, + "node_modules/vitest": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.6.tgz", + "integrity": "sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.6", + "@vitest/mocker": "3.2.6", + "@vitest/pretty-format": "^3.2.6", + "@vitest/runner": "3.2.6", + "@vitest/snapshot": "3.2.6", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.6", + "@vitest/ui": "3.2.6", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.38.tgz", + "integrity": "sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.38", + "@vue/compiler-sfc": "3.5.38", + "@vue/runtime-dom": "3.5.38", + "@vue/server-renderer": "3.5.38", + "@vue/shared": "3.5.38" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-draggable-next": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vue-draggable-next/-/vue-draggable-next-2.3.0.tgz", + "integrity": "sha512-ymbY0UIwfSdg0iDN/iyNNwUrTqZ/6KbPryzsvTNXBLuDCuOBdNijSK8yynNtmiSj6RapTPQfjLGQdJrZkzBd2w==", + "license": "MIT", + "peerDependencies": { + "sortablejs": "^1.14.0", + "vue": "^3.5.17" + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-i18n": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.6.tgz", + "integrity": "sha512-l0gE7Rfy0phCa5ChKYkOq543Wgd39BCK6hkktfr1Ed4D99oRkgPK9ffShASZdeC8OJxGfdWmpYoAaAH6iLEuIg==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.4.6", + "@intlify/devtools-types": "11.4.6", + "@intlify/shared": "11.4.6", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-scrollto": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/vue-scrollto/-/vue-scrollto-2.20.0.tgz", + "integrity": "sha512-7i+AGKJTThZnMEkhIPgrZjyAX+fXV7/rGdg+CV283uZZwCxwiMXaBLTmIc5RTA4uwGnT+E6eJle3mXQfM2OD3A==", + "license": "MIT", + "dependencies": { + "bezier-easing": "2.1.0" + } + }, + "node_modules/vue-tsc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.5.tgz", + "integrity": "sha512-Rzh/G2MmNlMSAMTiQEjDrsb4dgB/jbtEM47rVN2NtidF1dfb/q4w4QvpQBtW5+y3y5H27Hjh7deVwk+YB02fNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.5" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vuetify": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.12.8.tgz", + "integrity": "sha512-gvmOWeLd6CG7LVh2Qonft5YrIb7MpbKZglRjg0ItEloSbu6hfUII2RZmRSVxGKYdiXTf7J1hI3BRSHr8J9LkxA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/johnleider" + }, + "peerDependencies": { + "typescript": ">=4.7", + "vite-plugin-vuetify": ">=2.1.0", + "vue": "^3.5.0", + "webpack-plugin-vuetify": ">=3.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vite-plugin-vuetify": { + "optional": true + }, + "webpack-plugin-vuetify": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/w3c-xmlserializer/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..eb4167f --- /dev/null +++ b/web/package.json @@ -0,0 +1,63 @@ +{ + "name": "alphane-web", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "start": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview", + "test": "vitest", + "lint": "eslint . --ext .js,.ts,.vue --ignore-path ../.gitignore", + "check-types": "vue-tsc --noEmit" + }, + "dependencies": { + "@auth0/auth0-vue": "^2.5.0", + "@mdi/font": "^7.4.47", + "@tabler/icons-vue": "^3.36.0", + "@vueuse/core": "^13.9.0", + "@vueuse/router": "^13.9.0", + "axios": "^1.13.2", + "dompurify": "^3.3.3", + "grid-layout-plus": "^1.1.1", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "luxon": "^3.7.2", + "marked": "^17.0.5", + "md5": "^2.3.0", + "qs": "^6.14.0", + "validator": "^13.15.26", + "vue": "^3.4.21", + "vue-draggable-next": "^2.3.0", + "vue-i18n": "^11.3.0", + "vue-router": "^4.6.4", + "vue-scrollto": "^2.20.0", + "vuetify": "^3.11.4" + }, + "devDependencies": { + "@types/dompurify": "^3.0.5", + "@types/js-yaml": "^4.0.9", + "@types/lodash": "^4.17.21", + "@types/luxon": "^3.7.1", + "@types/md5": "^2.3.6", + "@types/qs": "^6.14.0", + "@types/validator": "^13.15.10", + "@typescript-eslint/eslint-plugin": "^8.50.1", + "@typescript-eslint/parser": "^8.50.1", + "@vitejs/plugin-vue": "^6.0.3", + "eslint": "^8.57.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "eslint-plugin-vue": "^9.33.0", + "jsdom": "^26.1.0", + "prettier": "^3.7.4", + "prettier-plugin-embed": "^0.5.1", + "prettier-plugin-sql": "^0.19.2", + "sass": "^1.97.1", + "typescript": "^5.9.3", + "vite": "^7.3.0", + "vite-plugin-vuetify": "^2.1.2", + "vitest": "^3.2.4", + "vue-tsc": "^3.2.1" + } +} \ No newline at end of file diff --git a/web/public/Logo.png b/web/public/Logo.png new file mode 100644 index 0000000..df593a1 Binary files /dev/null and b/web/public/Logo.png differ diff --git a/web/public/Logo.svg b/web/public/Logo.svg new file mode 100644 index 0000000..571edb1 --- /dev/null +++ b/web/public/Logo.svg @@ -0,0 +1,9 @@ + +WRAP + +Workflow : Routing : Approvals + + + diff --git a/web/public/SplashImage.png b/web/public/SplashImage.png new file mode 100644 index 0000000..af85145 Binary files /dev/null and b/web/public/SplashImage.png differ diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 0000000..5aebb70 Binary files /dev/null and b/web/public/favicon.ico differ diff --git a/web/public/yukon.svg b/web/public/yukon.svg new file mode 100644 index 0000000..fc4a4ac --- /dev/null +++ b/web/public/yukon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/web/src/App.vue b/web/src/App.vue new file mode 100644 index 0000000..3327592 --- /dev/null +++ b/web/src/App.vue @@ -0,0 +1,113 @@ + + + diff --git a/web/src/api/api-error.ts b/web/src/api/api-error.ts new file mode 100644 index 0000000..3c80112 --- /dev/null +++ b/web/src/api/api-error.ts @@ -0,0 +1,12 @@ +export class ApiError extends Error { + public readonly name = "ApiError" + + constructor( + message: string, + public readonly status: number + ) { + super(message) + } +} + +export default ApiError diff --git a/web/src/api/base-api.ts b/web/src/api/base-api.ts new file mode 100644 index 0000000..625d8dc --- /dev/null +++ b/web/src/api/base-api.ts @@ -0,0 +1,44 @@ +/** Keep in sync with api/src/controllers/base-controller.ts#ModelOrder */ +export type ModelOrder = + | [string, string] + | [string, string, string] + | [string, string, string, string] + | [string, string, string, string, string] + | [string, string, string, string, string, string] + +export type Policy = { + show: boolean + create: boolean + update: boolean + destroy: boolean +} + +export type WhereOptions = { + [K in Attributes]?: Model[K] | Model[K][] +} + +export type FiltersOptions = Partial + +export type QueryOptions = Partial<{ + where: WhereOptions + filters: FiltersOptions + order: ModelOrder[] + page: number + perPage: number +}> + +// Keep in sync with api/src/controllers/base-controller.ts +export const MAX_PER_PAGE = 1000 + +export type ApiResponseError = { field?: string; text: string } + +export type ApiResponseLegacy = { + data: T + errors: ApiResponseError[] + messages?: string[] +} + +export type ApiResponse = { + errors: ApiResponseError[] + messages?: string[] +} & TPayload diff --git a/web/src/api/current-user-api.ts b/web/src/api/current-user-api.ts new file mode 100644 index 0000000..ce67e1b --- /dev/null +++ b/web/src/api/current-user-api.ts @@ -0,0 +1,23 @@ +import http from "@/api/http-client" + +import { type Policy } from "@/api/base-api" +import { UserRoles, type User } from "@/api/users-api" + +export { UserRoles } + +export type UserAsShow = Pick< + User, + "id" | "email" | "firstName" | "lastName" | "displayName" | "roles" | "createdAt" | "updatedAt" +> + +export const currentUserApi = { + async get(): Promise<{ + user: UserAsShow + policy: Policy + }> { + const { data } = await http.get(`/api/current-user`) + return data + }, +} + +export default currentUserApi diff --git a/web/src/api/http-client.ts b/web/src/api/http-client.ts new file mode 100644 index 0000000..3556ae9 --- /dev/null +++ b/web/src/api/http-client.ts @@ -0,0 +1,46 @@ +import qs from "qs" +import axios from "axios" + +import { API_BASE_URL } from "@/config" +import auth0 from "@/plugins/auth0-plugin" +import ApiError from "@/api/api-error" + +export const httpClient = axios.create({ + baseURL: API_BASE_URL, + headers: { + "Content-Type": "application/json", + }, + paramsSerializer: { + serialize: (params) => { + return qs.stringify(params, { + arrayFormat: "indices", + strictNullHandling: true, + }) + }, + }, +}) + +httpClient.interceptors.request.use(async (config) => { + // Only add the Authorization header to requests that start with "/api" + if (config.url?.startsWith("/api")) { + const accessToken = await auth0.getAccessTokenSilently() + config.headers["Authorization"] = `Bearer ${accessToken}` + } + + return config +}) + +// Any status codes that falls outside the range of 2xx causes this function to trigger +httpClient.interceptors.response.use(null, async (error) => { + if (error?.error === "login_required") { + throw new ApiError("You must be logged in to access this endpoint", 401) + } else if (error?.response?.data?.message) { + throw new ApiError(error.response.data.message, error.response.status) + } else if (error.message) { + throw new ApiError(error.message, error.response?.status || 500) + } else { + throw new ApiError("An unknown error occurred", error.response?.status || 500) + } +}) + +export default httpClient diff --git a/web/src/api/status-api.ts b/web/src/api/status-api.ts new file mode 100644 index 0000000..bced29f --- /dev/null +++ b/web/src/api/status-api.ts @@ -0,0 +1,18 @@ +import http from "@/api/http-client" + +export type Status = { + RELEASE_TAG: string + GIT_COMMIT_HASH: string +} + +export const statusApi = { + /** + * Note: This is a public API route, and not protected by authentication + */ + async get(): Promise { + const { data } = await http.get("/_status") + return data + }, +} + +export default statusApi diff --git a/web/src/api/users-api.ts b/web/src/api/users-api.ts new file mode 100644 index 0000000..960890c --- /dev/null +++ b/web/src/api/users-api.ts @@ -0,0 +1,81 @@ +import http from "@/api/http-client" +import { + type FiltersOptions, + type ModelOrder, + type Policy, + type WhereOptions, +} from "@/api/base-api" + +/** Keep in sync with api/src/models/user.ts */ +export enum UserRoles { + SYSTEM_ADMIN = "system_admin", + USER = "user", +} + +export type User = { + id: number + email: string + firstName: string + lastName: string + displayName: string + roles: UserRoles[] + createdAt: string + updatedAt: string +} + +export type UserAsShow = Omit & {} + +export type UserWhereOptions = WhereOptions + +export type UserFiltersOptions = FiltersOptions<{ + search: string | string[] +}> + +export type UserQueryOptions = { + where?: UserWhereOptions + filters?: UserFiltersOptions + order?: ModelOrder[] + page?: number + perPage?: number +} + +export const usersApi = { + UserRoles, + async list(params: UserQueryOptions = {}): Promise<{ + users: User[] + totalCount: number + }> { + const { data } = await http.get("/api/users", { + params, + }) + return data + }, + async get(userId: number): Promise<{ + user: User + policy: Policy + }> { + const { data } = await http.get(`/api/users/${userId}`) + return data + }, + async create(attributes: Partial): Promise<{ + user: User + }> { + const { data } = await http.post("/api/users", attributes) + return data + }, + async update( + userId: number, + attributes: Partial + ): Promise<{ + user: User + }> { + const { data } = await http.patch(`/api/users/${userId}`, attributes) + return data + }, + async delete(userId: number): Promise { + const { data } = await http.delete(`/api/users/${userId}`) + return data + }, +} + +export default usersApi diff --git a/web/src/assets/app_logo_big.png b/web/src/assets/app_logo_big.png new file mode 100644 index 0000000..64d19e5 Binary files /dev/null and b/web/src/assets/app_logo_big.png differ diff --git a/web/src/assets/app_logo_small.png b/web/src/assets/app_logo_small.png new file mode 100644 index 0000000..713f514 Binary files /dev/null and b/web/src/assets/app_logo_small.png differ diff --git a/web/src/assets/app_logo_splash.png b/web/src/assets/app_logo_splash.png new file mode 100644 index 0000000..3d8eb69 Binary files /dev/null and b/web/src/assets/app_logo_splash.png differ diff --git a/web/src/components/common/AppBreadcrumbs.vue b/web/src/components/common/AppBreadcrumbs.vue new file mode 100644 index 0000000..ab1d14f --- /dev/null +++ b/web/src/components/common/AppBreadcrumbs.vue @@ -0,0 +1,55 @@ + + + + + + + + diff --git a/web/src/components/common/AppCard.vue b/web/src/components/common/AppCard.vue new file mode 100644 index 0000000..51c3d3e --- /dev/null +++ b/web/src/components/common/AppCard.vue @@ -0,0 +1,46 @@ + + + diff --git a/web/src/components/common/AppLogo.vue b/web/src/components/common/AppLogo.vue new file mode 100644 index 0000000..75426c4 --- /dev/null +++ b/web/src/components/common/AppLogo.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/web/src/components/common/AppSnackbar.vue b/web/src/components/common/AppSnackbar.vue new file mode 100644 index 0000000..49e3112 --- /dev/null +++ b/web/src/components/common/AppSnackbar.vue @@ -0,0 +1,82 @@ + + + diff --git a/web/src/components/common/CollapsibleCard.vue b/web/src/components/common/CollapsibleCard.vue new file mode 100644 index 0000000..a4d7fa9 --- /dev/null +++ b/web/src/components/common/CollapsibleCard.vue @@ -0,0 +1,50 @@ + + + diff --git a/web/src/components/common/ConfirmDialog.vue b/web/src/components/common/ConfirmDialog.vue new file mode 100644 index 0000000..5a0044d --- /dev/null +++ b/web/src/components/common/ConfirmDialog.vue @@ -0,0 +1,77 @@ + + + diff --git a/web/src/components/common/DescriptionElement.vue b/web/src/components/common/DescriptionElement.vue new file mode 100644 index 0000000..65e4f5b --- /dev/null +++ b/web/src/components/common/DescriptionElement.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/web/src/components/common/HeaderActionsCard.vue b/web/src/components/common/HeaderActionsCard.vue new file mode 100644 index 0000000..547a2d0 --- /dev/null +++ b/web/src/components/common/HeaderActionsCard.vue @@ -0,0 +1,73 @@ + + + diff --git a/web/src/components/common/HeaderActionsCardBody.vue b/web/src/components/common/HeaderActionsCardBody.vue new file mode 100644 index 0000000..09e9f75 --- /dev/null +++ b/web/src/components/common/HeaderActionsCardBody.vue @@ -0,0 +1,74 @@ + + + diff --git a/web/src/components/common/HeaderActionsFormCard.vue b/web/src/components/common/HeaderActionsFormCard.vue new file mode 100644 index 0000000..b2ea960 --- /dev/null +++ b/web/src/components/common/HeaderActionsFormCard.vue @@ -0,0 +1,101 @@ + + + diff --git a/web/src/components/common/PageLoader.vue b/web/src/components/common/PageLoader.vue new file mode 100644 index 0000000..fcb2961 --- /dev/null +++ b/web/src/components/common/PageLoader.vue @@ -0,0 +1,17 @@ + + + diff --git a/web/src/components/common/PercentageTextField.vue b/web/src/components/common/PercentageTextField.vue new file mode 100644 index 0000000..47cfb9b --- /dev/null +++ b/web/src/components/common/PercentageTextField.vue @@ -0,0 +1,85 @@ + + + diff --git a/web/src/components/common/ResponsiveDialog.vue b/web/src/components/common/ResponsiveDialog.vue new file mode 100644 index 0000000..b8d5d00 --- /dev/null +++ b/web/src/components/common/ResponsiveDialog.vue @@ -0,0 +1,83 @@ + + + diff --git a/web/src/components/common/StringDateInput.vue b/web/src/components/common/StringDateInput.vue new file mode 100644 index 0000000..178eb8f --- /dev/null +++ b/web/src/components/common/StringDateInput.vue @@ -0,0 +1,70 @@ + + + diff --git a/web/src/components/layout/DefaultAppBar.vue b/web/src/components/layout/DefaultAppBar.vue new file mode 100644 index 0000000..2a9a279 --- /dev/null +++ b/web/src/components/layout/DefaultAppBar.vue @@ -0,0 +1,40 @@ + + + diff --git a/web/src/components/layout/DefaultSideBar.vue b/web/src/components/layout/DefaultSideBar.vue new file mode 100644 index 0000000..100d1cd --- /dev/null +++ b/web/src/components/layout/DefaultSideBar.vue @@ -0,0 +1,70 @@ + + + diff --git a/web/src/components/layout/ExactingBreadcrumbs.vue b/web/src/components/layout/ExactingBreadcrumbs.vue new file mode 100644 index 0000000..53716e3 --- /dev/null +++ b/web/src/components/layout/ExactingBreadcrumbs.vue @@ -0,0 +1,69 @@ + + + + + + + diff --git a/web/src/components/layout/ProfileMenu.vue b/web/src/components/layout/ProfileMenu.vue new file mode 100644 index 0000000..6a0e913 --- /dev/null +++ b/web/src/components/layout/ProfileMenu.vue @@ -0,0 +1,160 @@ + + + diff --git a/web/src/components/layout/SearchMenu.vue b/web/src/components/layout/SearchMenu.vue new file mode 100644 index 0000000..46c2304 --- /dev/null +++ b/web/src/components/layout/SearchMenu.vue @@ -0,0 +1,59 @@ + + + diff --git a/web/src/components/users/UserEditCardForm.vue b/web/src/components/users/UserEditCardForm.vue new file mode 100644 index 0000000..ea12861 --- /dev/null +++ b/web/src/components/users/UserEditCardForm.vue @@ -0,0 +1,196 @@ + + + diff --git a/web/src/components/users/UserProfileCard.vue b/web/src/components/users/UserProfileCard.vue new file mode 100644 index 0000000..247d7d6 --- /dev/null +++ b/web/src/components/users/UserProfileCard.vue @@ -0,0 +1,86 @@ + + + diff --git a/web/src/components/users/UserRoleSelect.vue b/web/src/components/users/UserRoleSelect.vue new file mode 100644 index 0000000..b3e51ff --- /dev/null +++ b/web/src/components/users/UserRoleSelect.vue @@ -0,0 +1,30 @@ + + + diff --git a/web/src/components/users/UsersDataTableServer.vue b/web/src/components/users/UsersDataTableServer.vue new file mode 100644 index 0000000..33741fb --- /dev/null +++ b/web/src/components/users/UsersDataTableServer.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/web/src/config.ts b/web/src/config.ts new file mode 100644 index 0000000..1f98d72 --- /dev/null +++ b/web/src/config.ts @@ -0,0 +1,47 @@ +import { stripTrailingSlash } from "@/utils/strip-trailing-slash" + +export const ENVIRONMENT = import.meta.env.MODE + +const prodConfig = { + domain: "https://dev-7mdjzcgwirhocfwm.ca.auth0.com", + clientId: "TRlKzdNBynpo9tU1RSmnF0p8d3IEam4J", + audience: "alphane-api", + apiBaseUrl: "", + webSocketBaseUrl: "", + applicationName: "ALPHANE", +} + +const devConfig = { + domain: "https://dev-7mdjzcgwirhocfwm.ca.auth0.com", + clientId: "TRlKzdNBynpo9tU1RSmnF0p8d3IEam4J", + audience: "alphane-api", + apiBaseUrl: "http://localhost:3000", + webSocketBaseUrl: "ws://localhost:3000", + applicationName: "ALPHANE", +} + +const localProductionConfig = { + domain: "https://dev-7mdjzcgwirhocfwm.ca.auth0.com", + clientId: "TRlKzdNBynpo9tU1RSmnF0p8d3IEam4J", + audience: "alphane-api", + apiBaseUrl: "http://localhost:8080", + webSocketBaseUrl: "ws://localhost:8080", + applicationName: "ALPHANE (production)", +} + +let config = prodConfig + +if (ENVIRONMENT === "production" && window.location.host === "localhost:8080") { + config = localProductionConfig +} else if (window.location.host === "localhost:8080") { + config = devConfig +} + +export const APPLICATION_NAME = config.applicationName + +export const API_BASE_URL = config.apiBaseUrl +export const WEB_SOCKET_BASE_URL = config.webSocketBaseUrl + +export const AUTH0_DOMAIN = stripTrailingSlash(config.domain) +export const AUTH0_AUDIENCE = config.audience +export const AUTH0_CLIENT_ID = config.clientId diff --git a/web/src/directives/index.ts b/web/src/directives/index.ts new file mode 100644 index 0000000..6e3408c --- /dev/null +++ b/web/src/directives/index.ts @@ -0,0 +1,5 @@ +import { visible } from "./visible" + +export default { + visible, +} diff --git a/web/src/directives/visible.ts b/web/src/directives/visible.ts new file mode 100644 index 0000000..41a1b65 --- /dev/null +++ b/web/src/directives/visible.ts @@ -0,0 +1,27 @@ +import { DirectiveBinding } from "vue" + +/** + * Shows or hides an element using visibility property based on a boolean value. + * + * This makes helps loaders avoid downshift flickering when they trigger. + * + * See https://vuejs.org/guide/reusability/custom-directives.html#function-shorthand + * + * @usage + * ```html + * + * ``` + */ +export function visible(el: HTMLElement, binding: DirectiveBinding) { + if (binding.value) { + el.style.visibility = "visible" + } else { + el.style.visibility = "hidden" + } +} + +export default visible diff --git a/web/src/layouts/AdministrationLayout.vue b/web/src/layouts/AdministrationLayout.vue new file mode 100644 index 0000000..e8789e6 --- /dev/null +++ b/web/src/layouts/AdministrationLayout.vue @@ -0,0 +1,49 @@ + + + diff --git a/web/src/layouts/DefaultLayout.vue b/web/src/layouts/DefaultLayout.vue new file mode 100644 index 0000000..96aea7d --- /dev/null +++ b/web/src/layouts/DefaultLayout.vue @@ -0,0 +1,22 @@ + + + diff --git a/web/src/layouts/LayoutWithBreadcrumbs.vue b/web/src/layouts/LayoutWithBreadcrumbs.vue new file mode 100644 index 0000000..aa81ecd --- /dev/null +++ b/web/src/layouts/LayoutWithBreadcrumbs.vue @@ -0,0 +1,69 @@ + + + diff --git a/web/src/layouts/ProfileLayout.vue b/web/src/layouts/ProfileLayout.vue new file mode 100644 index 0000000..463b357 --- /dev/null +++ b/web/src/layouts/ProfileLayout.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/web/src/layouts/logo/AppLogo.vue b/web/src/layouts/logo/AppLogo.vue new file mode 100644 index 0000000..9985c18 --- /dev/null +++ b/web/src/layouts/logo/AppLogo.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/web/src/locales/en.js b/web/src/locales/en.js new file mode 100644 index 0000000..e146645 --- /dev/null +++ b/web/src/locales/en.js @@ -0,0 +1,8 @@ +export default { + user: { + roles: { + system_admin: "System Admin", + user: "User", + }, + }, +} diff --git a/web/src/main.ts b/web/src/main.ts new file mode 100644 index 0000000..a28f7a1 --- /dev/null +++ b/web/src/main.ts @@ -0,0 +1,25 @@ +import { createApp } from "vue" + +// Plugins +import vuetify from "@/plugins/vuetify-plugin" +import auth0 from "@/plugins/auth0-plugin" +import vueI18nPlugin from "@/plugins/vue-i18n-plugin" + +import "@/scss/style.scss" +import VueScrollTo from "vue-scrollto" +import directives from "@/directives" +import router from "@/router" + +import App from "@/App.vue" + +const app = createApp(App) +app.use(router).use(vuetify).use(auth0).use(vueI18nPlugin) + +app.directive("visible", directives.visible) + +app.mount("#app") + +app.use(VueScrollTo, { + duration: 1000, + easing: "ease", +}) diff --git a/web/src/pages/CallbackPage.vue b/web/src/pages/CallbackPage.vue new file mode 100644 index 0000000..28a099a --- /dev/null +++ b/web/src/pages/CallbackPage.vue @@ -0,0 +1,23 @@ + + + diff --git a/web/src/pages/DashboardPage.vue b/web/src/pages/DashboardPage.vue new file mode 100644 index 0000000..114c46a --- /dev/null +++ b/web/src/pages/DashboardPage.vue @@ -0,0 +1,16 @@ + + + diff --git a/web/src/pages/ProfilePage.vue b/web/src/pages/ProfilePage.vue new file mode 100644 index 0000000..5ee1be1 --- /dev/null +++ b/web/src/pages/ProfilePage.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/web/src/pages/SignInPage.vue b/web/src/pages/SignInPage.vue new file mode 100644 index 0000000..1122518 --- /dev/null +++ b/web/src/pages/SignInPage.vue @@ -0,0 +1,115 @@ + + + diff --git a/web/src/pages/StatusPage.vue b/web/src/pages/StatusPage.vue new file mode 100644 index 0000000..8bf3efb --- /dev/null +++ b/web/src/pages/StatusPage.vue @@ -0,0 +1,93 @@ + + + diff --git a/web/src/pages/administration/AdministrationDashboardPage.vue b/web/src/pages/administration/AdministrationDashboardPage.vue new file mode 100644 index 0000000..9a9dc8e --- /dev/null +++ b/web/src/pages/administration/AdministrationDashboardPage.vue @@ -0,0 +1,118 @@ + + + diff --git a/web/src/pages/administration/SettingsPage.vue b/web/src/pages/administration/SettingsPage.vue new file mode 100644 index 0000000..25f351b --- /dev/null +++ b/web/src/pages/administration/SettingsPage.vue @@ -0,0 +1,14 @@ + + + diff --git a/web/src/pages/administration/UsersPage.vue b/web/src/pages/administration/UsersPage.vue new file mode 100644 index 0000000..7dedbee --- /dev/null +++ b/web/src/pages/administration/UsersPage.vue @@ -0,0 +1,46 @@ + + + diff --git a/web/src/pages/administration/users/UserEditPage.vue b/web/src/pages/administration/users/UserEditPage.vue new file mode 100644 index 0000000..d68a3e3 --- /dev/null +++ b/web/src/pages/administration/users/UserEditPage.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/web/src/pages/administration/users/UserNewPage.vue b/web/src/pages/administration/users/UserNewPage.vue new file mode 100644 index 0000000..3ee6a05 --- /dev/null +++ b/web/src/pages/administration/users/UserNewPage.vue @@ -0,0 +1,353 @@ + + + diff --git a/web/src/pages/errors/ForbiddenPage.vue b/web/src/pages/errors/ForbiddenPage.vue new file mode 100644 index 0000000..a8baa60 --- /dev/null +++ b/web/src/pages/errors/ForbiddenPage.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/web/src/pages/errors/InternalServerErrorPage.vue b/web/src/pages/errors/InternalServerErrorPage.vue new file mode 100644 index 0000000..f0af657 --- /dev/null +++ b/web/src/pages/errors/InternalServerErrorPage.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/web/src/pages/errors/NotFoundPage.vue b/web/src/pages/errors/NotFoundPage.vue new file mode 100644 index 0000000..9ab6e0c --- /dev/null +++ b/web/src/pages/errors/NotFoundPage.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/web/src/pages/errors/UnauthorizedPage.vue b/web/src/pages/errors/UnauthorizedPage.vue new file mode 100644 index 0000000..91ff9de --- /dev/null +++ b/web/src/pages/errors/UnauthorizedPage.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/web/src/pages/profile/ProfileEditPage.vue b/web/src/pages/profile/ProfileEditPage.vue new file mode 100644 index 0000000..f49184a --- /dev/null +++ b/web/src/pages/profile/ProfileEditPage.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/web/src/plugins/auth0-plugin.ts b/web/src/plugins/auth0-plugin.ts new file mode 100644 index 0000000..ca8e12f --- /dev/null +++ b/web/src/plugins/auth0-plugin.ts @@ -0,0 +1,14 @@ +import { createAuth0 } from "@auth0/auth0-vue" + +import { AUTH0_AUDIENCE, AUTH0_CLIENT_ID, AUTH0_DOMAIN, ENVIRONMENT } from "@/config" + +// See https://auth0.github.io/auth0-vue/#md:add-login-to-your-application +export default createAuth0({ + domain: AUTH0_DOMAIN, + clientId: AUTH0_CLIENT_ID, + authorizationParams: { + audience: AUTH0_AUDIENCE, + redirect_uri: `${window.location.origin}/callback`, + }, + cacheLocation: ENVIRONMENT === "development" ? "localstorage" : "memory", +}) diff --git a/web/src/plugins/vue-i18n-plugin.ts b/web/src/plugins/vue-i18n-plugin.ts new file mode 100644 index 0000000..741d0e5 --- /dev/null +++ b/web/src/plugins/vue-i18n-plugin.ts @@ -0,0 +1,13 @@ +import { createI18n } from "vue-i18n" + +// I'd prefer to use yaml, or even json, but I can't get them to import at the moment +// This might be a TypeScript issue, or I might need a yaml plugin. +import en from "@/locales/en.js" + +export default createI18n({ + legacy: false, // support composition api + locale: "en", + messages: { + en, + }, +}) diff --git a/web/src/plugins/vuetify-plugin.ts b/web/src/plugins/vuetify-plugin.ts new file mode 100644 index 0000000..d0c9cee --- /dev/null +++ b/web/src/plugins/vuetify-plugin.ts @@ -0,0 +1,86 @@ +/** + * plugins/vuetify.js + * + * Framework documentation: https://vuetifyjs.com` + */ + +// Styles +import "@mdi/font/css/materialdesignicons.css" +import "vuetify/styles" + +// ComposablesF +import { createVuetify } from "vuetify" +import * as components from "vuetify/components" +import * as directives from "vuetify/directives" +import * as labsComponents from "vuetify/labs/components" + +import { + DARK_BLUE_THEME, + DARK_AQUA_THEME, + DARK_ORANGE_THEME, + DARK_PURPLE_THEME, + DARK_GREEN_THEME, + DARK_CYAN_THEME, +} from "@/theme/DarkTheme" + +// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides +export default createVuetify({ + components: { + ...components, + ...labsComponents, + }, + directives, + theme: { + defaultTheme: "DARK_AQUA_THEME", + themes: { + DARK_BLUE_THEME, + DARK_AQUA_THEME, + DARK_ORANGE_THEME, + DARK_PURPLE_THEME, + DARK_GREEN_THEME, + DARK_CYAN_THEME, + }, + }, + defaults: { + VCard: { + rounded: "md", + }, + VTextField: { + variant: "outlined", + density: "comfortable", + color: "primary", + }, + VTextarea: { + variant: "outlined", + density: "comfortable", + color: "primary", + }, + VFileInput: { + variant: "outlined", + density: "comfortable", + color: "primary", + prependIcon: null, + }, + VSelect: { + variant: "outlined", + density: "comfortable", + color: "primary", + }, + VCombobox: { + variant: "outlined", + density: "comfortable", + color: "primary", + }, + VAutocomplete: { + variant: "outlined", + density: "comfortable", + color: "primary", + }, + VListItem: { + minHeight: "45px", + }, + VTooltip: { + location: "top", + }, + }, +}) diff --git a/web/src/router.ts b/web/src/router.ts new file mode 100644 index 0000000..b26128e --- /dev/null +++ b/web/src/router.ts @@ -0,0 +1,152 @@ +import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router" +import { authGuard } from "@auth0/auth0-vue" + +import { APPLICATION_NAME } from "@/config" +import administrationRoutes from "@/routes/administration-routes" +import { authorizationGuard } from "@/utils/authorization-guards" + +const routes: RouteRecordRaw[] = [ + { + path: "/", + name: "SignInPage", + component: () => import("@/pages/SignInPage.vue"), + meta: { requiresAuth: false }, + }, + { + path: "/callback", + name: "CallbackPage", + component: () => import("@/pages/CallbackPage.vue"), + meta: { requiresAuth: false }, + }, + { + path: "/", + component: () => import("@/layouts/DefaultLayout.vue"), + children: [ + { + path: "", + redirect: "sign-in", + }, + { + name: "DashboardPage", + path: "dashboard", + component: () => import("@/pages/DashboardPage.vue"), + meta: { + title: "Dashboard", + }, + }, + { + path: "", + component: () => import("@/layouts/LayoutWithBreadcrumbs.vue"), + children: [ + { + path: "profile", + component: () => import("@/layouts/ProfileLayout.vue"), + meta: { + title: "Profile", + }, + children: [ + { + path: "", + name: "ProfilePage", + redirect: { name: "profile/ProfileEditPage" }, + }, + ], + }, + + { + path: "profile/edit", + name: "profile/ProfileEditPage", + component: () => import("@/pages/profile/ProfileEditPage.vue"), + meta: { + title: "Edit Profile", + }, + }, + ], + }, + ], + }, + ...administrationRoutes, + { + name: "StatusPage", + path: "/status", + component: () => import("@/pages/StatusPage.vue"), + meta: { + title: "Status", + requiresAuth: false, + }, + }, + { + path: "/errors/unauthorized", + name: "errors/UnauthorizedPage", + component: () => import("@/pages/errors/UnauthorizedPage.vue"), + meta: { + title: "Unauthorized", + requiresAuth: false, + }, + }, + { + path: "/errors/forbidden", + name: "errors/ForbiddenPage", + component: () => import("@/pages/errors/ForbiddenPage.vue"), + meta: { + title: "Forbidden", + requiresAuth: false, + }, + }, + { + path: "/errors/internal-server-error", + name: "errors/InternalServerErrorPage", + component: () => import("@/pages/errors/InternalServerErrorPage.vue"), + meta: { + title: "Internal Server Error", + requiresAuth: false, + }, + }, + { + path: "/errors/not-found", + name: "errors/NotFoundPage", + component: () => import("@/pages/errors/NotFoundPage.vue"), + meta: { + title: "Not Found", + requiresAuth: false, + }, + }, + { + path: "/:pathMatch(.*)*", + redirect: "/errors/not-found", + }, +] + +const router = createRouter({ + history: createWebHistory(), + routes, +}) + +router.onError((error, to) => { + if ( + error.message.includes("Failed to fetch dynamically imported module") || + error.message.includes("Importing a module script failed") + ) { + window.location.href = to.fullPath + } +}) + +router.beforeEach(async (to) => { + if (to.meta && to.meta.title) { + document.title = `${APPLICATION_NAME} - ${to.meta.title}` + } else { + document.title = APPLICATION_NAME + } + + if (to.meta.requiresAuth === false) return true + + const isAuthenticated = await authGuard(to) + if (!isAuthenticated) return false + + const isAuthorized = await authorizationGuard(to) + if (!isAuthorized) return "/errors/forbidden" + + return true +}) + +export default router diff --git a/web/src/routes/administration-routes.ts b/web/src/routes/administration-routes.ts new file mode 100644 index 0000000..2e38182 --- /dev/null +++ b/web/src/routes/administration-routes.ts @@ -0,0 +1,50 @@ +import { RouteRecordRaw } from "vue-router" + +import { isSystemAdmin } from "@/utils/authorization-guards" + +export const administrationRoutes: Readonly = [ + { + path: "/administration", + component: () => import("@/layouts/AdministrationLayout.vue"), + meta: { + guards: [isSystemAdmin], + }, + children: [ + { + path: "", + name: "administration/AdministrationDashboardPage", + component: () => import("@/pages/administration/AdministrationDashboardPage.vue"), + }, + { + path: "users", + name: "administration/UsersPage", + component: () => import("@/pages/administration/UsersPage.vue"), + }, + { + path: "users/new", + name: "administration/users/UserNewPage", + component: () => import("@/pages/administration/users/UserNewPage.vue"), + props: true, + }, + { + path: "users/:userId", + name: "administration/users/UserPage", + component: () => import("@/pages/administration/users/UserEditPage.vue"), + props: true, + }, + { + path: "users/:userId/edit", + name: "administration/users/UserEditPage", + component: () => import("@/pages/administration/users/UserEditPage.vue"), + props: true, + }, + { + path: "settings", + name: "administration/SettingsPage", + component: () => import("@/pages/administration/SettingsPage.vue"), + }, + ], + }, +] + +export default administrationRoutes diff --git a/web/src/scss/_override.scss b/web/src/scss/_override.scss new file mode 100644 index 0000000..c9e294c --- /dev/null +++ b/web/src/scss/_override.scss @@ -0,0 +1,204 @@ +@use "./variables" as *; + +html { + .bg-success { + color: $white !important; + } + + .bg-primary { + color: $white !important; + } + + .bg-secondary { + color: $white !important; + } + + .bg-warning { + color: $white !important; + } + + .bg-secondary-gradient { + background: linear-gradient(287deg, rgb(var(--v-theme-primary)) .54%, #1bcaff 100.84%); + } +} + + + + +.border, +.v-divider { + border-color: rgba(var(--v-border-color)) !important; +} + +.avtar-border { + border: 2px solid rgb(var(--v-theme-surface)) !important; +} + +.subtext { + font-size: $font-size-root; + line-height: 1.75rem; +} + +.v-dialog { + &.dialog-mw { + max-width: 800px; + } +} + +.round-40 { + height: 40px; + width: 40px; +} + +.round-56 { + height: 56px; + width: 56px; +} + +.round-48 { + height: 48px; + width: 48px; +} + +.round-30 { + height: 30px; + width: 30px; +} + +.lh-0 { + line-height: 0 !important; +} + + +.lh-28 { + line-height: 28px !important; +} + +.lh-32 { + line-height: 32px !important; +} + +.space-p-96 { + padding: 96px 0 !important; + +} + +.ps-96 { + padding-inline-start: 96px !important; +} + +.pt-96 { + padding-top: 96px !important; +} + +.end-0 { + inset-inline-end: 0; +} + +.top-0 { + top: 0; +} + +.no-scrollbar { + height: calc(100vh - 350px); +} + +.msg-chat-height { + height: calc(-500px + 100vh); +} + +@media screen and (max-width:991px) { + .overflow-x-reposive { + overflow-x: scroll; + overflow-y: hidden; + } + + .border-m-none { + border: 0 !important + } +} + +@media screen and (max-height:767px) { + .msg-chat-height { + height: calc(-315px + 100vh); + } + +} + +.max-h-600 { + max-height: 600px; + height: calc(100vh - 100px); +} + + +.custom-hover-primary { + .iconify { + color: rgb(255, 255, 255) !important; + + @media screen and (max-width:991px) { + color: rgba(var(--v-theme-textPrimary), 0.8) !important + } + } + + &:hover { + background-color: rgba(var(--v-theme-lightprimary), 0.1); + + .iconify { + @media screen and (max-width:991px) { + color: rgb(var(--v-theme-primary)) !important + } + } + } +} +.custom-hover-primary-white { + .iconify { + color: rgb(255, 255, 255) !important; + + + } + + &:hover { + background-color: rgba(var(--v-theme-lightprimary), 0.1); + + } +} + +.no-icon { + + .v-input__prepend, + .v-input__append { + display: none !important; + } +} + +.bg-white { + background-color: rgb(255, 255, 255) !important; +} + +.v-badge { + &.x-small-badge { + .v-badge__badge { + height: 6px !important; + width: 6px !important; + } + + } +} + +.one-line { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; +} + +.two-line { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.z-1 { + z-index: 1; +} \ No newline at end of file diff --git a/web/src/scss/_variables.scss b/web/src/scss/_variables.scss new file mode 100644 index 0000000..7fe2161 --- /dev/null +++ b/web/src/scss/_variables.scss @@ -0,0 +1,150 @@ +@use "sass:math"; +@use "sass:map"; +@use "sass:meta"; +@use "vuetify/lib/styles/tools/functions" as *; + +// Custom Variables +// colors +$white: #fff !default; + +// cards +$card-title-size: 18px !default; + +$body-font-family: "Overpass", sans-serif !default; +$border-radius-root: 10px; +$btn-font-weight: 400 !default; +$btn-letter-spacing: 0 !default; + +// Global Shadow +$box-shadow: + rgba(145 158 171 / 30%) 0px 0px 2px 0px, + rgba(145 158 171 / 12%) 0px 12px 24px -4px; + +// Global Radius as per breakeven point + +@forward "vuetify/settings" with ( + $color-pack: false !default, + // Global font size and border radius + $font-size-root: 1rem, + $border-radius-root: $border-radius-root, + $body-font-family: $body-font-family, + $heading-font-family: $body-font-family !default, + $button-height: 40px, + // 👉 Typography + $typography: ( + "h1": ( + "size": 2.25rem, + "weight": 600, + "line-height": 2.75rem, + "font-family": inherit, + ), + "h2": ( + "size": 1.875rem, + "weight": 500, + "line-height": 2.25rem, + "font-family": inherit, + ), + "h3": ( + "size": 1.5rem, + "weight": 500, + "line-height": 2rem, + "font-family": inherit, + ), + "h4": ( + "size": 1.3125rem, + "weight": 500, + "line-height": 1.6rem, + "font-family": inherit, + ), + "h5": ( + "size": 1.125rem, + "weight": 500, + "line-height": 1.6rem, + "font-family": inherit, + ), + "h6": ( + "size": 1rem, + "weight": 500, + "line-height": 1.2rem, + "font-family": inherit, + ), + "subtitle-1": ( + "size": 0.875rem, + "weight": 400, + "line-height": 1.1rem, + "font-family": inherit, + ), + "subtitle-2": ( + "size": 0.75rem, + "weight": 400, + "line-height": 1rem, + "font-family": inherit, + ), + "body-1": ( + "size": 0.875rem, + "weight": 400, + "font-family": inherit, + ), + "body-2": ( + "size": 0.75rem, + "weight": 400, + "font-family": inherit, + ), + "button": ( + "size": 0.875rem, + "weight": 500, + "font-family": inherit, + "text-transform": capitalize, + ), + "caption": ( + "size": 0.75rem, + "weight": 400, + "font-family": inherit, + ), + "overline": ( + "size": 0.75rem, + "weight": 500, + "font-family": inherit, + "text-transform": uppercase, + ), + ) + !default, + // 👉 Button + $button-border-radius: $border-radius-root !default, + $button-text-letter-spacing: 0 !default, + $button-text-transform: capitalize, + $button-elevation: ( + "default": 0, + "hover": 4, + "active": 8, + ) + !default, + + // 👉 Tooltip + $tooltip-background-color: #212121 !default, + $tooltip-text-color: rgb(var(--v-theme-on-primary)) !default, + $tooltip-font-size: 0.75rem !default, + $tooltip-border-radius: 4px !default, + $tooltip-padding: 4px 8px !default, + + // 👉 Rounded + $rounded: ( + 0: 0, + "sm": $border-radius-root * 0.5, + null: $border-radius-root, + "md": $border-radius-root * 1, + "lg": $border-radius-root * 2, + "xl": $border-radius-root * 6, + "pill": 9999px, + "circle": 50%, + ), + + // 👉 Card + // $card-color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity)) !default, + $card-elevation: 10 !default, + $card-title-line-height: 1.6 !default, + $card-text-padding: 24px !default, + $card-item-padding: 30px 30px 24px !default, + $card-actions-padding: 10px 24px 24px !default, + $card-subtitle-opacity: 1 !default // $card-border-color $border-color-root +); diff --git a/web/src/scss/components/_VAlert.scss b/web/src/scss/components/_VAlert.scss new file mode 100644 index 0000000..8415aed --- /dev/null +++ b/web/src/scss/components/_VAlert.scss @@ -0,0 +1,24 @@ +.single-line-alert { + .v-alert__close, + .v-alert__prepend { + align-self: center !important; + } +} + +@media (max-width: 500px) { + .single-line-alert { + display: flex; + flex-wrap: wrap; + + .v-alert__append { + margin-inline-start: 0px; + } + .v-alert__close { + margin-left: auto; + } + .v-alert__content { + width: 100%; + margin-top: 5px; + } + } +} diff --git a/web/src/scss/components/_VBreadcrumb.scss b/web/src/scss/components/_VBreadcrumb.scss new file mode 100644 index 0000000..81b4ad1 --- /dev/null +++ b/web/src/scss/components/_VBreadcrumb.scss @@ -0,0 +1,9 @@ + +.v-breadcrumbs{ + .v-breadcrumbs-divider{ + padding: 0 0 !important; + } + .v-breadcrumbs-item--link{ + text-decoration: none; + } +} diff --git a/web/src/scss/components/_VButtons.scss b/web/src/scss/components/_VButtons.scss new file mode 100644 index 0000000..dd2183e --- /dev/null +++ b/web/src/scss/components/_VButtons.scss @@ -0,0 +1,22 @@ +.v-btn-group .v-btn { + height: inherit !important; +} + +.v-btn-group { + border-color: rgb(var(--v-theme-borderColor)) !important; +} +.v-btn{ + text-transform: capitalize; + letter-spacing: 0; + border-radius: 30px; + + &.v-btn--variant-elevated{ + box-shadow: none !important; + } + .v-btn--slim{ + padding: 0 15px; + } +} +.v-btn--elevated:hover{ + box-shadow: none; +} diff --git a/web/src/scss/components/_VCard.scss b/web/src/scss/components/_VCard.scss new file mode 100644 index 0000000..3bf5eb6 --- /dev/null +++ b/web/src/scss/components/_VCard.scss @@ -0,0 +1,69 @@ +// Outline Card +.v-card--variant-outlined { + border-color: rgba(var(--v-theme-borderColor)) !important; +} + +.v-card--variant-elevated, +.v-card--variant-flat { + color: rgb(var(--v-theme-textPrimary)); +} + +.card-hover { + transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; + &:hover { + scale: 1.01; + transition: all 0.1s ease-in 0s; + } +} + +.v-card { + width: 100%; + overflow: visible; + .color-inherits { + color: inherit; + } + .feature-card { + .v-responsive__content { + height: 100%; + } + } + .v-timeline-divider__before,.v-timeline-divider__after { + background: rgba(var(--v-border-color), 1); + } + .v-card-text{ + padding: 24px 24px; + } + .v-card-item{ + padding: 24px 24px; + } +} + +// Theme cards +.cardBordered { + .v-card { + box-shadow: none !important; + border: 1px solid rgb(var(--v-theme-borderColor)); + } +} + +.elevation-o-card{ + .v-card-item{ + padding: 0.625rem 1rem; + } +} + +.card-title{ + font-size: 18px; + font-weight: 500; + color:rgb(var(--v-theme-textPrimary)); +} +.card-subtitle{ + font-size: 14px; + font-weight: 400; + color:rgb(var(--v-theme-textSecondary)); +} +.dark-card-title{ + font-size: 18px; + font-weight: 500; + color:rgb(var(--v-theme-textPrimary)); +} \ No newline at end of file diff --git a/web/src/scss/components/_VCarousel.scss b/web/src/scss/components/_VCarousel.scss new file mode 100644 index 0000000..42306be --- /dev/null +++ b/web/src/scss/components/_VCarousel.scss @@ -0,0 +1,3 @@ +.theme-carousel .v-carousel__progress { + position: absolute; +} diff --git a/web/src/scss/components/_VDatatable.scss b/web/src/scss/components/_VDatatable.scss new file mode 100644 index 0000000..a2c9214 --- /dev/null +++ b/web/src/scss/components/_VDatatable.scss @@ -0,0 +1,3 @@ +.v-pagination__item--is-active .v-btn__overlay { + opacity: 0.15 !important; +} \ No newline at end of file diff --git a/web/src/scss/components/_VDatatables.scss b/web/src/scss/components/_VDatatables.scss new file mode 100644 index 0000000..0ccafd9 --- /dev/null +++ b/web/src/scss/components/_VDatatables.scss @@ -0,0 +1,99 @@ +.v-table { + + &.datatabels { + + &.productlist { + .v-data-table-header__content span { + color: rgb(var(--v-theme-textPrimary)); + } + + .v-toolbar { + .v-input__control { + max-width: 300px; + } + + .v-toolbar__content { + height: auto !important; + } + } + + thead tr th:first-child { + padding-left: 0px !important; + } + + tbody tr td:first-child { + padding-left: 0px !important; + } + tbody tr td{ + padding: 15px; + } + + } + + .v-selection-control--dirty .v-selection-control__input>.v-icon { + color: rgb(var(--v-theme-primary)); + } + } + +} + + +@media screen and (max-width:1368px) { + + .v-table { + + &.datatabels { + + &.productlist { + .v-data-table-header__content span { + color: rgb(var(--v-theme-textPrimary)); + } + + table { + tbody { + tr { + + td { + padding: 14px 5px !important; + + &:first-child { + padding-left: 15px !important; + } + } + } + } + + thead { + tr { + th { + padding: 14px 5px !important; + + &:first-child { + padding-left: 15px !important; + } + } + } + } + } + + + } + } + + } + +} + +.v-pagination { + .v-pagination__list { + .v-pagination__item--is-active { + .v-btn { + .v-btn__overlay { + opacity: 0; + } + + background-color: rgb(var(--v-theme-grey100)) !important; + } + } + } +} \ No newline at end of file diff --git a/web/src/scss/components/_VExpansionpanel.scss b/web/src/scss/components/_VExpansionpanel.scss new file mode 100644 index 0000000..de59dc7 --- /dev/null +++ b/web/src/scss/components/_VExpansionpanel.scss @@ -0,0 +1,6 @@ +.v-expansion-panel-title__overlay{ + background: rgba(var(--v-theme-primary)); +} +.v-expansion-panel:not(:first-child)::after { + border-color: transparent !important; +} \ No newline at end of file diff --git a/web/src/scss/components/_VField.scss b/web/src/scss/components/_VField.scss new file mode 100644 index 0000000..a3dad00 --- /dev/null +++ b/web/src/scss/components/_VField.scss @@ -0,0 +1,29 @@ +@use "../variables" as *; + +.v-field--variant-outlined .v-field__outline__start.v-locale--is-ltr, +.v-locale--is-ltr .v-field--variant-outlined .v-field__outline__start { + border-radius: $border-radius-root 0 0 $border-radius-root; +} + +.v-field--variant-outlined .v-field__outline__end.v-locale--is-ltr, +.v-locale--is-ltr .v-field--variant-outlined .v-field__outline__end { + border-radius: 0 $border-radius-root $border-radius-root 0; +} + +.v-field { + font-size: 14px; + color: rgba(var(--v-theme-textPrimary)); +} + +// select outlined +.v-field--variant-outlined .v-field__outline__start, +.v-field--variant-outlined .v-field__outline__notch::before, +.v-field--variant-outlined .v-field__outline__notch::after, +.v-field--variant-outlined .v-field__outline__end { + opacity: 1; +} + + +.v-field--active .v-label.v-field-label{ + color: rgb(var(--v-theme-textPrimary)); +} \ No newline at end of file diff --git a/web/src/scss/components/_VInput.scss b/web/src/scss/components/_VInput.scss new file mode 100644 index 0000000..77ad91a --- /dev/null +++ b/web/src/scss/components/_VInput.scss @@ -0,0 +1,35 @@ +// variant +.v-input--density-default, +.v-field--variant-solo, +.v-field--variant-filled { + --v-input-control-height: 51px; + --v-input-padding-top: 14px; +} + +// comfortable +.v-input--density-comfortable { + --v-input-control-height: 44px; +} + +// compact +.v-input--density-compact { + --v-input-padding-top: 10px; +} +.v-label { + font-size: 14px; + opacity: 0.7; + font-weight: 500 !important; +} +.v-switch .v-label, +.v-checkbox .v-label { + opacity: 1; +} + +.v-text-field__suffix { + opacity: 1; + padding-left: 20px; +} + +.shadow-none .v-field--variant-solo { + box-shadow: none !important; +} diff --git a/web/src/scss/components/_VLabs.scss b/web/src/scss/components/_VLabs.scss new file mode 100644 index 0000000..633d421 --- /dev/null +++ b/web/src/scss/components/_VLabs.scss @@ -0,0 +1,18 @@ +.v-time-picker-clock{ + background: rgb(var(--v-theme-grey100)) ; +} +.v-time-picker-controls__ampm__btn.v-btn.v-btn--density-default{ + border: 0 !important; +} +.v-stepper-header,.v-stepper.v-sheet{ + box-shadow: none !important; +} + +.v-time-picker-controls__time__btn.v-btn--density-default.v-btn { + width: 55px !important; + height: 55px !important; + font-size: 30px; +} +.v-time-picker-controls__time__separator { + font-size: 36px !important; +} \ No newline at end of file diff --git a/web/src/scss/components/_VList.scss b/web/src/scss/components/_VList.scss new file mode 100644 index 0000000..1f88ca7 --- /dev/null +++ b/web/src/scss/components/_VList.scss @@ -0,0 +1,34 @@ +.v-list.theme-list { + .v-list-item:hover > .v-list-item__overlay { + opacity: 1; + z-index: 1; + } + .v-list-item--variant-text { + .v-list-item__overlay { + background: rgb(var(--v-theme-hoverColor)); + } + } + + .v-list-item__prepend, + .v-list-item__content { + z-index: 2; + } + + .v-list-item__overlay { + background-color: rgb(var(--v-theme-hoverColor)); + } + .v-list-item.v-list-item--active{ + .v-list-item__overlay{ + opacity: 1; + } + } + + .mail-items{ + min-height: 40px !important; + margin-bottom: 5px !important; + } +} + +.v-list-item-title{ + font-size: 14px; +} diff --git a/web/src/scss/components/_VNavigationDrawer.scss b/web/src/scss/components/_VNavigationDrawer.scss new file mode 100644 index 0000000..9994ae9 --- /dev/null +++ b/web/src/scss/components/_VNavigationDrawer.scss @@ -0,0 +1,3 @@ +.v-navigation-drawer__scrim.fade-transition-leave-to { + display: none; +} diff --git a/web/src/scss/components/_VSelectionControl.scss b/web/src/scss/components/_VSelectionControl.scss new file mode 100644 index 0000000..5c080a9 --- /dev/null +++ b/web/src/scss/components/_VSelectionControl.scss @@ -0,0 +1,6 @@ +// For checkbox & radios +.v-selection-control__input > .v-icon.mdi-checkbox-blank-outline, +.v-selection-control__input > .v-icon.mdi-radiobox-blank { + color: rgb(var(--v-theme-inputBorder)); + opacity: 1; +} diff --git a/web/src/scss/components/_VShadow.scss b/web/src/scss/components/_VShadow.scss new file mode 100644 index 0000000..e834869 --- /dev/null +++ b/web/src/scss/components/_VShadow.scss @@ -0,0 +1,33 @@ +@use "../variables" as *; + +.elevation-9 { + box-shadow: rgb(0 0 0 / 5%) 0px 9px 17.5px !important; +} + +.elevation-10 { + box-shadow: $box-shadow !important; +} +.elevation-1 { + box-shadow:0px 12px 30px -2px rgba(58,75,116,0.14) !important +} +.elevation-2 { + box-shadow:0px 24px 24px -12px rgba(0, 0, 0, .05) !important +} +.elevation-3 { + box-shadow: rgba(145,158,171,0.2) 0px 0px 2px 0px, rgba(145,158,171,0.12) 0px 12px 24px -4px !important; +} +.elevation-4{ + box-shadow: 0px 12px 12px -6px rgba(0,0,0,0.15) !important; +} +.elevation-5 +{ + box-shadow: 1px 0 7px rgba(0, 0, 0, .05)!important; +} + +.primary-shadow { + box-shadow: rgba(var(--v-theme-primary), 0.30) 0px 12px 14px 0px; + &:hover { + box-shadow: none; + } +} + diff --git a/web/src/scss/components/_VStepper.scss b/web/src/scss/components/_VStepper.scss new file mode 100644 index 0000000..06e5f17 --- /dev/null +++ b/web/src/scss/components/_VStepper.scss @@ -0,0 +1,8 @@ +.v-stepper-item--selected .v-stepper-item__avatar.v-avatar, .v-stepper-item--complete .v-stepper-item__avatar.v-avatar { + background: rgb(var(--v-theme-primary)) !important; +} + +.v-stepper-item__avatar.v-avatar { + background: rgba(var(--v-theme-primary), var(--v-medium-emphasis-opacity)) !important; + color: rgb(var(--v-theme-on-primary)) !important; +} \ No newline at end of file diff --git a/web/src/scss/components/_VSwitch.scss b/web/src/scss/components/_VSwitch.scss new file mode 100644 index 0000000..ce58d9b --- /dev/null +++ b/web/src/scss/components/_VSwitch.scss @@ -0,0 +1,48 @@ +.v-selection-control.v-selection-control--density-default { + .v-switch__track, + .v-switch__thumb { + background-color: rgb(var(--v-theme-grey200)); + } + &.v-selection-control--dirty { + .v-selection-control__wrapper.text-primary { + .v-switch__track { + background-color: rgba(var(--v-theme-primary), 0.6); + } + .v-switch__thumb { + background-color: rgb(var(--v-theme-primary)); + } + } + .v-selection-control__wrapper.text-secondary { + .v-switch__track { + background-color: rgba(var(--v-theme-secondary), 0.6); + } + .v-switch__thumb { + background-color: rgb(var(--v-theme-secondary)); + } + } + .v-selection-control__wrapper.text-warning { + .v-switch__track { + background-color: rgba(var(--v-theme-warning), 0.6); + } + .v-switch__thumb { + background-color: rgb(var(--v-theme-warning)); + } + } + .v-selection-control__wrapper.text-error { + .v-switch__track { + background-color: rgba(var(--v-theme-error), 0.6); + } + .v-switch__thumb { + background-color: rgb(var(--v-theme-error)); + } + } + .v-selection-control__wrapper.text-success { + .v-switch__track { + background-color: rgba(var(--v-theme-success), 0.6); + } + .v-switch__thumb { + background-color: rgb(var(--v-theme-success)); + } + } + } +} diff --git a/web/src/scss/components/_VTable.scss b/web/src/scss/components/_VTable.scss new file mode 100644 index 0000000..61a5c0b --- /dev/null +++ b/web/src/scss/components/_VTable.scss @@ -0,0 +1,99 @@ +.v-table .v-table__wrapper > table > tbody > tr:not(:last-child) > td, +.v-table .v-table__wrapper > table > tbody > tr:not(:last-child) > th, +.v-table .v-table__wrapper > table > thead > tr:last-child > th { + border-bottom: thin solid rgba(var(--v-border-color)) !important; +} + +.v-data-table{ + th.v-data-table__th{ + font-size:16px; + color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity)); + } + td.v-data-table__td{ + font-size: 14px; + text-wrap: nowrap; + } + .v-data-table-footer{ + padding: 15px 8px; + } + .v-data-table-header__sort-badge{ + background-color:rgb(var(--v-theme-borderColor)) !important; + } + .tdhead{ + font-size:16px; + } +} +@media screen and (max-width:767px) { + .v-data-table-footer{ + justify-content: center; + } +} + +.v-table { + + + &.ticket-table { + + table { + thead { + th { + font-weight: 600 !important; + } + } + + tbody { + tr { + + td { + padding: 16px 16px !important; + } + } + } + } + + } + + &.invoice-table { + .v-table__wrapper { + table { + thead { + th { + font-weight: 600 !important; + padding: 0px 24px !important; + + &:first-child { + padding-left: 0 !important; + } + + &:last-child { + padding-right: 0 !important; + } + + } + } + + tbody { + tr { + + td { + padding: 8px 24px !important; + + &:first-child { + padding-left: 0 !important; + } + + &:last-child { + padding-right: 0 !important; + } + } + } + } + } + } + + + } + + + +} \ No newline at end of file diff --git a/web/src/scss/components/_VTabs.scss b/web/src/scss/components/_VTabs.scss new file mode 100644 index 0000000..9932645 --- /dev/null +++ b/web/src/scss/components/_VTabs.scss @@ -0,0 +1,14 @@ +@use "../variables" as *; + +.theme-tab { + &.v-tabs { + .v-tab { + border-radius: $border-radius-root !important; + min-width: auto !important; + &.v-slide-group-item--active { + background: rgb(var(--v-theme-primary)); + + } + } + } +} \ No newline at end of file diff --git a/web/src/scss/components/_VTextField.scss b/web/src/scss/components/_VTextField.scss new file mode 100644 index 0000000..bbc52a7 --- /dev/null +++ b/web/src/scss/components/_VTextField.scss @@ -0,0 +1,13 @@ +.v-text-field input { + font-size: 0.875rem; +} +.v-field__outline { + color: rgb(var(--v-theme-inputBorder)); + --v-field-border-opacity: 1 !important; +} +.input { + .v-field--variant-outlined { + background-color: rgba(0, 0, 0, 0.025); + } +} + diff --git a/web/src/scss/components/_VTextarea.scss b/web/src/scss/components/_VTextarea.scss new file mode 100644 index 0000000..b611c41 --- /dev/null +++ b/web/src/scss/components/_VTextarea.scss @@ -0,0 +1,7 @@ +.v-textarea input { + font-size: 0.875rem; + font-weight: 500; + &::placeholder { + color: rgba(0, 0, 0, 0.38); + } +} diff --git a/web/src/scss/front/_general.scss b/web/src/scss/front/_general.scss new file mode 100644 index 0000000..46d795b --- /dev/null +++ b/web/src/scss/front/_general.scss @@ -0,0 +1,597 @@ +@use "../variables" as *; + +@keyframes slideup { + 0% { + transform: translate3d(0, 0, 0); + } + + 100% { + transform: translate3d(0px, -100%, 0px); + } +} + +.animateDown { + animation: 35s linear 0s infinite normal none running slideDown; +} + +@keyframes slideDown { + 0% { + transform: translate3d(0, -100%, 0); + } + + 100% { + transform: translate3d(0px, 0, 0px); + } +} + +// OfferBar +.offerbar { + position: relative; + top: 0; + width: 100%; + z-index: 999; + + .white-btn { + background-color: rgba(var(--v-theme-surface), 0.15); + font-weight: 700; + height: 25px; + } + + + + &:before { + background-repeat: no-repeat; + content: ''; + position: absolute; + background-image: url('@/assets/images/front-pages/background/left-shape.png'); + bottom: 0; + height: 40px; + left: 0; + width: 325px; + } + + &:after { + background-repeat: no-repeat; + content: ''; + position: absolute; + background-image: url('@/assets/images/front-pages/background/right-shape.png'); + bottom: 0; + right: 17%; + width: 325px; + top: 0; + background-size: contain; + } +} + +// +// frameworks +// +.slider-group { + animation: slide 45s linear infinite; +} + +.marquee1-group { + animation: marquee 45s linear infinite; +} + +.marquee2-group { + animation: marquee2 45s linear infinite; +} + + +@keyframes slide { + 0% { + transform: translate3d(0, 0, 0); + } + + 100% { + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes marquee { + 0% { + transform: translate3d(0, 0, 0); + } + + 100% { + transform: translate3d(-2086px, 0, 0); + } +} + +@keyframes marquee2 { + 0% { + transform: translate3d(-2086px, 0, 0) + } + + 100% { + transform: translate3d(0, 0, 0) + } +} + +.front-wraper { + overflow: hidden; + + .underline-link { + text-underline-offset: 4px; + } + + .underline-link-6 { + text-underline-offset: 6px; + text-decoration-thickness: 2px; + } + + .main-banner { + min-width: 1300px; + overflow: hidden; + max-height: 700px; + height: calc(100vh - 100px); + + img { + max-width: 100%; + height: auto; + + } + } + + .team { + &:hover { + .intro { + opacity: 1; + } + } + + .intro { + opacity: 0; + bottom: 16px; + inset-inline-start: .75rem; + inset-inline-end: .75rem; + transition: 0.5s; + } + } + + // Revenue Products + .feature-tabs { + .v-slide-group__content { + gap: 16px; + padding-bottom: 56px; + } + + + + .v-btn { + background-color: rgba(var(--v-theme-surface)); + padding: 16px 24px; + border-radius: 12px !important; + font-size: 16px; + box-shadow: 0px 24px 24px -12px rgba(0, 0, 0, .05); + } + + .v-tab--selected { + background-color: rgba(var(--v-theme-primary)); + box-shadow: 0px 24px 24px -12px rgba(99, 91, 255, .15); + + .v-btn__content { + color: #fff; + + .v-tab__slider { + display: none; + } + } + } + } + + .v-container { + &.max-width-1218 { + max-width: 1218px !important; + } + + } + + .v-container { + &.max-width-800 { + max-width: 800px !important; + } + + &.max-width-1000 { + max-width: 1000px !important; + } + } + + .max-w-600 { + max-width: 600px !important; + } + + .template { + .left-widget { + position: absolute; + top: 96px; + inset-inline-start: -40px; + max-height: 400px; + width: auto; + } + + .right-widget { + position: absolute; + top: 96px; + inset-inline-end: -40px; + max-height: 400px; + width: auto; + } + } + + + .feature-tabs { + .v-tab__slider { + top: 0; + bottom: unset; + } + + &.v-tabs--density-default { + --v-tabs-height: auto; + } + } + + .feature-tabs-expansion { + + .v-expansion-panel-text__wrapper { + padding: 0px 0px 16px; + } + + .v-expansion-panel-title { + padding: 16px 0px; + } + + .v-expansion-panel { + background-color: transparent !important; + } + + .v-expansion-panel--active:not(:first-child), + .v-expansion-panel--active+.v-expansion-panel { + margin-top: 0; + } + } + + .leader-slider { + .carousel__slide { + padding: 0 15px; + } + + .carousel__viewport { + margin: 0 -15px; + padding-bottom: 30px; + } + + .carousel__prev, + .carousel__next { + top: -85px; + width: 100%; + justify-content: end; + margin: 0; + transform: none; + display: flex; + justify-content: center + } + + .carousel__next { + height: 48px; + width: 48px; + border-radius: 50%; + background: rgb(var(--v-theme-lightprimary)); + + } + + .carousel__prev { + height: 48px; + width: 48px; + border-radius: 50%; + background: rgb(var(--v-theme-lightprimary)); + right: 65px; + left: unset; + } + } + + .our-template { + .carousel__slide { + padding: 0 15px 40px; + } + + .carousel__viewport { + margin: 0 -106px; + } + } + + .testimonials { + + .carousel__prev, + .carousel__next { + width: 100%; + justify-content: end; + margin: 0; + transform: translateY(75px); + display: flex; + justify-content: center; + bottom: 0; + } + + .carousel__next { + height: 32px; + width: 32px; + border-radius: 50%; + background: rgb(var(--v-theme-background)); + left: -60%; + } + + .carousel__prev { + height: 32px; + width: 32px; + border-radius: 50%; + background: rgb(var(--v-theme-background)); + right: 65px; + left: -74%; + } + + .carousel { + padding-bottom: 25px; + } + + .slide-counter { + position: relative; + bottom: -4px; + left: 50px; + z-index: 2; + font-size: 15px; + opacity: 0.7; + } + } + + .social-icon { + svg { + path { + fill: rgb(255, 255, 255); + + &:hover { + fill: rgb(var(--v-theme-primary)); + } + } + } + } + + .package { + .v-list-item { + min-height: 35px !important; + } + } + + .lp-faq { + .v-expansion-panel-title__icon { + .v-icon { + font-size: 20px; + opacity: 0.5 + } + } + + .v-expansion-panels:not(.v-expansion-panels--variant-accordion)> :first-child:not(:last-child):not(.v-expansion-panel--active):not(.v-expansion-panel--before-active) { + border-bottom-left-radius: 8px !important; + border-bottom-right-radius: 8px !important; + } + + .v-expansion-panels:not(.v-expansion-panels--variant-accordion)> :not(:first-child):not(:last-child):not(.v-expansion-panel--active):not(.v-expansion-panel--after-active) { + border-top-left-radius: 8px !important; + border-top-right-radius: 8px !important; + } + + .v-expansion-panels:not(.v-expansion-panels--variant-accordion)> :not(:first-child):not(:last-child):not(.v-expansion-panel--active):not(.v-expansion-panel--before-active) { + border-bottom-left-radius: 8px !important; + border-bottom-right-radius: 8px !important; + } + + .v-expansion-panel--active:not(:first-child), + .v-expansion-panel--active+.v-expansion-panel { + margin-top: 0px !important; + } + } + + + .animted-img { + position: absolute; + z-index: 9; + top: 0%; + animation: mover 5s infinite alternate; + } + + .animted-img-2 { + position: absolute; + z-index: 9; + top: -35px; + inset-inline-end: 15px; + animation: mover 5s infinite alternate; + } + + @keyframes mover { + 0% { + transform: translateY(0); + } + + 100% { + transform: translateY(-10px); + } + } + + .carousel__pagination { + .carousel__pagination-button { + padding: 6px; + + &::after { + height: 8px; + width: 8px; + border-radius: 50%; + background-color: transparent; + background-color: rgb(var(--v-theme-textPrimary)); + opacity: 0.25; + } + + &:hover { + &::after { + background-color: #000; + opacity: 1; + } + } + } + + .carousel__pagination-button--active { + &::after { + background-color: #000; + opacity: 1; + } + } + } + + .carousel { + z-index: 2; + } +} + + + +.v-btn--size-default { + &.nav-links { + font-size: $font-size-root !important; + + .v-btn__overlay { + display: none; + } + + &:hover { + color: rgb(var(--v-theme-primary)) !important; + } + } +} + + + +.light-primary { + background-color: rgb(var(--v-theme-primary), 0.1); +} + +.announce-close { + position: absolute; + right: 15px; + +} + +.text-align-start{ + text-align: start; +} + +@media screen and (max-width:1199px) { + .ps-96 { + padding-inline-start: 60px !important; + } + + .space-p-96 { + padding: 55px 0 !important; + } + + .pt-96 { + padding-top: 55px !important; + } + + .offerbar { + &:after { + background-image: none; + } + } + + .offerbar:after, + .offerbar:before { + display: none; + } + +} + +@media screen and (max-width:1024px) { + .front-wraper .testimonials .slide-counter { + left: 67px; + } + + .space-p-96 { + padding: 40px 0 !important; + } + + .pt-96 { + padding-top: 40px !important; + } + + .front-wraper .bg-collection { + background-image: none; + } + + + + .front-wraper .our-template .carousel__viewport { + margin: 0 0px; + } + + .ps-96 { + padding-inline-start: 20px !important; + padding-inline-end: 20px !important; + } + +} + +@media screen and (max-width:991px) { + .text-align-start{ + text-align: center; + } +} + +@media screen and (max-width:767px) { + .technology { + .round-54 { + height: 45px; + width: 45px; + + img { + height: 22px; + } + } + } + + .front-wraper { + .display-2 { + font-size: 32px; + line-height: normal; + } + + .display-1 { + font-size: 32px; + line-height: normal; + } + } + + .front-wraper .leader-slider .carousel__viewport { + margin: 0 0px; + } + + .announce-close { + bottom: 8px; + } + + .text-48 { + font-size: 30px !important; + line-height: 40px !important; + } + + .text-56 { + font-size: 35px !important; + line-height: 40px !important; + } + + .team { + .intro { + opacity: 1 !important; + } + } + +} \ No newline at end of file diff --git a/web/src/scss/front/_header.scss b/web/src/scss/front/_header.scss new file mode 100644 index 0000000..0952c88 --- /dev/null +++ b/web/src/scss/front/_header.scss @@ -0,0 +1,104 @@ +@use "../variables" as *; + +.front-lp-header { + + .v-toolbar{ + background: rgb(var(--v-theme-surface)); + } + + &.v-app-bar .v-toolbar__content { + padding: 0; + + } + + .v-toolbar__content { + background: transparent !important; + box-shadow: none !important; + } + + &.v-toolbar { + background: transparent !important; + top: 0 !important; + } + + .v-toolbar { + background: transparent !important; + } + + &.sticky-header { + position: fixed !important; + top: 0 !important; + transition: 0.5s; + background-color: rgba(var(--v-theme-surface)) !important; + box-shadow: 0 4px 29px -11px #3a4b7424 !important; + + } + +} +// +// mega menu +// +.white-btn{ + background-color: #769CFF; +} +.front_wrapper { + + &.v-menu .v-overlay__content { + margin: 0 auto; + left: 0 !important; + right: 0; + } + .megamenu { + &::before { + content: ''; + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 96%; + background-color: rgba(55, 114, 255, 0.2); + border-radius: 7px; + opacity: 0; + } + .v-btn { + top: 50%; + transform: translateY(-50%); + z-index: 1; + left: 0; + right: 0; + min-width: 100px; + opacity: 0; + font-size: 13px; + } + &:hover { + &::before, + .v-btn { + opacity: 1; + } + } + } +} +.lp-drawer { + &.v-navigation-drawer { + top: 0 !important; + height: 100% !important; + z-index: 1007 !important; + } +} + +.lp-mobile-sidebar { + .v-list { + .v-list-item__content { + overflow: inherit; + } + } + .v-list-group__items .v-list-item { + padding-inline-start: 25px !important; + } +} + +.v-btn--size-default { + &.nav-links { + font-size: $font-size-root !important; + } +} \ No newline at end of file diff --git a/web/src/scss/layout/_container.scss b/web/src/scss/layout/_container.scss new file mode 100644 index 0000000..d9fc39a --- /dev/null +++ b/web/src/scss/layout/_container.scss @@ -0,0 +1,40 @@ +html { + overflow-y: auto; +} +.v-main{ + background:rgb(var(--v-theme-background)) !important; ; +} +@media (max-width: 1279px) { + .v-main { + margin: 0 10px; + } +} + +.cursor-pointer { + cursor: pointer; +} + +.page-wrapper { + min-height: calc(100vh - 100px); + padding: 24px; + // border-radius: $border-radius-root; + @media screen and (max-width: 767px) { + padding: 20px 10px; + } +} + +.maxWidth { + max-width: 1200px; + margin: 0 auto; +} + +.fixed-width { + max-width: 1300px; +} + +.right-pos-img { + position: absolute; + right: 0; + top: 0; + height: 100%; +} diff --git a/web/src/scss/layout/_customizer.scss b/web/src/scss/layout/_customizer.scss new file mode 100644 index 0000000..1e3b9af --- /dev/null +++ b/web/src/scss/layout/_customizer.scss @@ -0,0 +1,93 @@ +.v-btn.customizer-btn { + position: fixed; + bottom: 30px; + right: 30px; + border-radius: 50%; + // .icon-tabler-settings { + // animation: progress-circular-rotate 1.4s linear infinite; + // transform-origin: center center; + // transition: all 0.2s ease-in-out; + // } +} + +.btn-group-custom { + &.v-btn-group { + height: 66px !important; + overflow: unset !important; + .v-btn { + height: 66px !important; + padding: 0 20px; + border: 1px solid rgb(var(--v-theme-borderColor), 0.7) !important; + transition: all 0.1s ease-in 0s; + &:hover { + transform: scale(1.05); + } + &.text-primary { + .v-btn__overlay { + background: transparent !important; + } + .icon { + color: rgb(var(--v-theme-primary)) !important; + fill: rgb(var(--v-theme-primary), 0.2); + } + color: rgb(var(--v-theme-primary)) !important; + } + } + } +} + +.hover-btns { + transition: all 0.1s ease-in 0s; + &:hover { + transform: scale(1.05); + } +} +// all theme colors +.v-avatar.themeBlue, +.v-avatar.themeDarkBlue { + background: #1e88e5; +} +.v-avatar.themeAqua, +.v-avatar.themeDarkAqua { + background: #0074ba; +} + +.v-avatar.themePurple, +.v-avatar.themeDarkPurple { + background: #763ebd; +} +.v-avatar.themeGreen, +.v-avatar.themeDarkGreen { + background: #0a7ea4; +} + +.v-avatar.themeCyan, +.v-avatar.themeDarkCyan { + background: #01c0c8; +} + +.v-avatar.themeOrange, +.v-avatar.themeDarkOrange { + background: #fa896b; +} + + +.DARK_BLUE_THEME, .DARK_AQUA_THEME, .DARK_ORANGE_THEME, .DARK_PURPLE_THEME, .DARK_GREEN_THEME, .DARK_CYAN_THEME { + .togglethemeBlue { + display: block !important; + } + + .togglethemeDarkBlue { + display: none !important; + } +} + +.BLUE_THEME, .AQUA_THEME, .ORANGE_THEME, .PURPLE_THEME, .GREEN_THEME, .CYAN_THEME { + .togglethemeDarkBlue { + display: block !important; + } + + .togglethemeBlue { + display: none !important; + } +} \ No newline at end of file diff --git a/web/src/scss/layout/_dark.scss b/web/src/scss/layout/_dark.scss new file mode 100644 index 0000000..feb2a94 --- /dev/null +++ b/web/src/scss/layout/_dark.scss @@ -0,0 +1,81 @@ +// theme : dark +div[class*='v-theme--DARK_'] { + .smallCap { + color: rgb(var(--v-theme-textSecondary)); + } + + .elevation-10 { + box-shadow: rgb(145 158 171 / 30%) 0px 0px 2px 0px, rgb(145 158 171 / 2%) 0px 12px 24px -4px !important; + } + .v-field__outline{ + --v-field-border-opacity: 0.38 !important; + } + + .front-wraper{ + .bg-background{ + background-color: rgb(var(--v-theme-hoverColor)) !important; + } + + .front-dark{ + &.bg-textPrimary{ + background-color: rgb(var(--v-theme-surface)) !important; + } + } + .bg-textPrimary{ + background-color: rgb(var(--v-theme-textSecondary)) !important; + } + } + + + #vector-map .dxm-layers path { + fill: #7C8FAC !important; + } + + .svgMap-map-wrapper { + .svgMap-country { + stroke: #878585; + fill: #1A2537 !important; + + &#svgMap-map-country-IN { + fill: rgb(var(--v-theme-secondary)) !important; + } + + &#svgMap-map-country-AF { + fill: rgb(var(--v-theme-purple)) !important; + } + + &#svgMap-map-country-US { + fill: rgb(var(--v-theme-primary)) !important; + } + } + + .svgMap-map-controls-zoom { + background: #c9d6de !important; + } + .svgMap-control-button{ + background-color: rgb(var(--v-theme-textSecondary)) !important; + } + + } + .dark-card-title{ + color:rgb(var(--v-theme-surface)); + } + + .fc{ + .fc-button-primary:not(:disabled).fc-button-active { + background-color: rgb(var(--v-theme-grey100)); + } + .fc-button-group { + >.fc-button { + &:hover,&:focus{ + background-color: rgba(var(--v-theme-grey100)); + color: #fff; + .fc-icon{ + color: #fff; + } + } + } + } + } + +} diff --git a/web/src/scss/layout/_horizontal.scss b/web/src/scss/layout/_horizontal.scss new file mode 100644 index 0000000..87273e0 --- /dev/null +++ b/web/src/scss/layout/_horizontal.scss @@ -0,0 +1,231 @@ +@use "../variables" as *; + +.horizontalLayout { + .v-main { + margin: 0 16px !important; + + @media screen and (max-width: 767px) { + margin: 0 10px !important; + } + } +} + +.horizontal-header { + &.v-app-bar .v-toolbar__content { + padding: 0; + display: flex; + justify-content: space-between; + } + + .maxWidth { + @media screen and (max-width: 1199px) { + padding: 0 8px !important; + } + } + +} + +.ddMenu { + &.ddLevel-1 { + .navItem { + .navItemLink { + .dot { + height: 6px; + width: 6px; + background-color: rgb(var(--v-theme-textSecondary)); + border-radius: 50%; + margin-inline-end: 8px !important; + } + } + + &:hover { + .dot { + background-color: rgb(var(--v-theme-secondary)); + } + } + } + } + + &.ddLevel-2 { + .navItem { + .navItemLink { + .dot { + height: 6px; + width: 6px; + background-color: rgb(var(--v-theme-textSecondary)); + border-radius: 50%; + margin-inline-end: 8px !important; + } + } + + &:hover { + .dot { + background-color: rgb(var(--v-theme-secondary)); + } + } + } + } +} + + +.horizontalMenu { + .v-toolbar__content { + max-width: 1270px; + margin: 0 auto; + } + + .navItem:has(.ddMenu.ddLevel-1 li a.router-link-active) { + background-color: rgb(var(--v-theme-secondary)) !important; + border-radius: 9999px; + + .navcollapse { + color: rgba(255, 255, 255); + } + } + +} + +.mobile-menu { + .v-navigation-drawer { + margin-top: -70px !important; + height: 100vh !important; + z-index: 2000 !important; + } +} + +@media (min-width: 960px) { + .horizontalMenu { + margin-top: 65px; + margin-bottom: -70px; + + .maxWidth { + .horizontal-navbar { + max-width: 1160px; + } + } + } + + .horizontal-navbar { + padding: 16px 0; + margin: 0px auto; + align-items: center; + display: flex; + z-index: 11; + font-size: 0.875rem; + position: relative; + + ul { + padding: 0px; + margin: 0px; + } + + .ddMenu { + li { + a { + color: rgb(var(--v-theme-textPrimary)) !important; + } + } + } + + li { + list-style: none; + + a { + text-decoration: none; + display: flex; + align-items: center; + padding: 10px 13px; + height: 40px; + + .navIcon { + margin-right: 10px; + display: flex; + } + + .ddIcon { + margin-top: 2px; + } + + &.router-link-exact-active { + background-color: transparent; + color: rgba(var(--v-theme-secondary)) !important; + + .dot { + background-color: rgb(var(--v-theme-secondary)) !important; + } + + } + + } + } + + .navItem { + position: relative; + + .single-link { + &:hover { + color: rgb(var(--v-theme-secondary)) !important; + } + } + + + .ddMenu { + .navItem { + .navcollapse { + &:hover { + color: rgb(var(--v-theme-secondary)) !important; + } + } + } + } + + + } + + .ddMenu { + position: absolute; + width: 230px; + display: none; + top: 40px; + padding: 10px; + z-index: 1; + background-color: rgb(var(--v-theme-surface)); + box-shadow: $box-shadow; + border-radius: $border-radius-root; + + li { + margin-bottom: 3px; + } + } + + .ddLevel-2, + .ddLevel-3 { + top: -5px; + left: 212px; + } + + .navItem:hover { + + >.ddMenu { + display: block; + } + } + + >li:hover { + background-color: rgb(var(--v-theme-lightprimary)); + border-radius: 9999px; + + >.navItemLink { + color: rgb(var(--v-theme-secondary)); + opacity: 1; + } + } + + .router-link-exact-active { + color: rgb(var(--v-theme-secondary)); + font-weight: 500; + background-color: rgb(var(--v-theme-lightprimary)); + border-radius: $border-radius-root; + } + } +} \ No newline at end of file diff --git a/web/src/scss/layout/_reboot.scss b/web/src/scss/layout/_reboot.scss new file mode 100644 index 0000000..f4e4321 --- /dev/null +++ b/web/src/scss/layout/_reboot.scss @@ -0,0 +1,109 @@ +.h-100 { + height: 100%; +} + +.w-100 { + width: 100%; +} + +.h-100vh { + height: 100vh; +} + +.gap-2 { + gap: 8px; +} + +.gap-3 { + gap: 16px; +} + +.gap-4 { + gap: 24px; +} + +.text-white { + color: rgb(255, 255, 255) !important; +} + +// border +.border-bottom { + border-bottom: 1px solid rgba(0, 0, 0, .05); +} + +.opacity-1 { + opacity: 1 !important; +} + +.opacity-50 { + opacity: 0.5; +} + +.z-auto.v-card { + z-index: auto; +} + +.obj-cover { + object-fit: cover; +} + +.cursor-move { + cursor: move; +} + +body { + cursor: default; +} + +input:not([type="checkbox"]):not([type="radio"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="file"]):not([type="range"]):not([type="color"]), +textarea, +[contenteditable="true"] { + cursor: text; +} + +//Date time picker +input[type="date"], +input[type="time"] { + display: block !important; +} + +input[type="date"]::-webkit-calendar-picker-indicator, +input[type="time"]::-webkit-calendar-picker-indicator { + display: block !important; +} + +.ProseMirror { + min-height: 150px; +} + +.upload-btn-wrapper { + width: 150px; + height: 140px; + margin: 0 auto; + box-shadow: 0 0.5rem 1.5rem 0.5rem rgba(0, 0, 0, 0.075); + + input[type=file] { + position: absolute; + left: 0; + top: 0; + opacity: 0; + height: 100%; + width: 100%; + } +} + +.bg-transparent { + background-color: transparent !important; +} +.bg-dark{ + background-color: rgba(0, 0, 0, .08); +} +.bg-white-opacity{ + background-color: rgba(255, 255, 255, 0.2); +} + +@media screen and (max-width:1368px) and (min-width:1200px) { + .space-20 { + padding: 30px 20px !important; + } +} \ No newline at end of file diff --git a/web/src/scss/layout/_rtl.scss b/web/src/scss/layout/_rtl.scss new file mode 100644 index 0000000..b245ee8 --- /dev/null +++ b/web/src/scss/layout/_rtl.scss @@ -0,0 +1,341 @@ +.v-locale--is-rtl { + .customizer-btn { + left: 30px; + right: unset; + } + + .horizontal-navbar .icon-box { + margin-left: 12px; + } + + + + .bg-img-1 { + position: absolute; + bottom: 0; + left: 0; + right: unset !important; + transform: scaleX(-1); + } + + .ml-1 { + margin-left: unset !important; + margin-right: 4px; + } + + .ml-2 { + margin-left: unset !important; + margin-right: 8px; + } + + .mr-1 { + margin-right: unset !important; + margin-left: 4px; + } + + .mr-2 { + margin-right: unset !important; + margin-left: 8px; + } + + .mr-sm-2 { + margin-right: unset !important; + margin-left: 8px; + } + + .mr-3 { + margin-right: unset !important; + margin-left: 12px !important; + } + + .mr-4 { + margin-right: unset !important; + margin-left: 16px !important; + } + + .ml-3 { + margin-left: unset !important; + margin-right: 12px !important; + } + + .mr-auto { + margin-left: auto !important; + margin-right: unset !important; + } + + .ml-4 { + margin-left: unset !important; + margin-right: 16px; + } + + .ml-sm-4 { + margin-left: unset !important; + margin-right: 16px; + } + + .ml-md-4 { + margin-left: unset !important; + margin-right: 16px; + } + + .ml-sm-3 { + margin-left: unset !important; + margin-right: 12px; + } + + + .ml-5 { + margin-left: unset !important; + margin-right: 20px; + } + + .ml-6 { + margin-left: unset !important; + margin-right: 24px; + } + + .ml-10 { + margin-left: unset !important; + margin-right: 40px; + } + + .pl-1 { + padding-left: unset !important; + padding-right: 4px !important; + } + + .pl-2 { + padding-left: unset !important; + padding-right: 8px !important; + } + + .pr-2 { + padding-left: 8px !important; + } + + .pr-4 { + padding-left: 16px !important; + padding-right: unset !important; + } + + .pl-4 { + padding-left: unset !important; + padding-right: 16px !important; + } + + .right-pos-img { + right: unset; + left: 0; + transform: scaleX(-1); + top: 0; + } + + .badg-dotDetail { + left: 0; + right: -8px; + } + + .text-right { + text-align: left !important; + } + + .text-sm-right, + .text-md-right { + text-align: left !important; + } + + .text-sm-left { + text-align: right !important; + } + + .text-left { + text-align: right !important; + } + + .ml-auto, + .ml-sm-auto { + margin-left: unset !important; + margin-right: auto !important; + } + + .justify-start { + justify-content: flex-end !important; + } + + .vertical-table .v-table>.v-table__wrapper>table>tbody>tr>th { + border-left: thin solid rgba(var(--v-border-color), 1) !important; + } + + .authentication .auth-header { + left: unset; + right: 0; + } + + .horizontal-navbar li a { + padding: 10px 13px; + } + + .horizontal-navbar { + li { + margin-right: 0; + margin-left: 15px; + } + } + + // &.v-menu .v-overlay__content, + // &.search_popup .v-overlay__content, + // &.language_dropdown .v-overlay__content, + // &.notification_popup .v-overlay__content, + // &.profile_popup .v-overlay__content { + // left: inherit; + // } + + .related-Product { + .carousel__prev.navarrow { + top: 0; + right: unset !important; + left: 0; + } + + .carousel__next.navarrow { + top: 0; + right: unset !important; + left: 45px; + } + } + + //RTL mode minisidebar hover to active scrollbar + .ps--active-y>.ps__rail-y { + right: unset !important; + left: 0; + } + + //RTL mode sidebar scrollbar on right side + .left-customizer { + .ps__rail-y { + right: 0 !important; + } + } + + .horizontal-navbar .ddMenu { + padding: 10px 15px 10px 0px; + } + + .v-list-group__items { + .iconClass { + position: relative; + left: unset; + right: -2px; + } + } + + @media (min-width: 960px) { + + .horizontal-navbar .ddLevel-2, + .horizontal-navbar .ddLevel-3 { + top: -5px; + right: 212px; + } + + .horizontal-navbar li a .navIcon { + margin-right: 0; + margin-left: 10px; + } + } + + .leftSidebar .profile-name h5 { + direction: ltr; + } + + .horizontal-navbar { + .ddMenu { + .navItemLink { + padding-right: 15px; + } + } + } + + @media screen and (max-width:1279px) { + .mini-sidebar { + .v-navigation-drawer.v-navigation-drawer--right { + width: 270px !important; + } + + .v-navigation-drawer.v-navigation-drawer--left { + width: 320px !important; + } + } + + } + + .rtlImg { + transform: scaleX(-1); + } + + .marquee1-group { + animation: marquee-rtl 45s linear infinite; + } + + .marquee2-group { + animation: marquee2-rtl 45s linear infinite; + } + + @keyframes marquee-rtl { + 0% { + transform: translate3d(0, 0, 0); + } + + 100% { + transform: translate3d(2086px, 0, 0); + } + } + + @keyframes marquee2-rtl { + 0% { + transform: translate3d(2086px, 0, 0); + } + + 100% { + transform: translate3d(0, 0, 0); + } + } + + .front-wraper { + .testimonials { + .slide-counter { + left: -50px; + } + + .carousel__prev { + right: -74%; + left: unset; + + .rtlnav { + transform: scaleX(-1); + } + } + + .carousel__next { + right: -60%; + left: unset; + + .rtlnav { + transform: scaleX(-1); + } + + } + } + } + + //For Rtl Chart + .rtl-me-n7 { + margin-inline-start: -28px !important; + margin-inline-end: unset !important; + } + + .profile-img::before { + left: unset; + right: 14px; + } + +} \ No newline at end of file diff --git a/web/src/scss/layout/_sidebar.scss b/web/src/scss/layout/_sidebar.scss new file mode 100644 index 0000000..b1d279c --- /dev/null +++ b/web/src/scss/layout/_sidebar.scss @@ -0,0 +1,374 @@ +@use "../variables" as *; + +/*This is for the logo*/ +.leftSidebar { + box-shadow: 0 3px 4px 0 rgba(0, 0, 0, .03), 0 0 1px 0 rgba(0, 0, 0, .1); + + .logo { + padding-left: 7px; + } + + .mini-icon { + display: none; + } + + .mini-text { + display: block; + } + + .profile { + background: url("@/assets/images/backgrounds/user-info.jpg") no-repeat; + } + + .profile-name { + background: rgba(0, 0, 0, 0.5); + margin-top: -6px; + height: 35px; + + h5 { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + } + + .v-list--density-default .v-list-subheader { + padding-inline-start: 0 !important; + } +} + +.verticalLayout { + .logo { + width: 250px; + + @media screen and (max-width:1024px) { + width: auto; + } + } +} + +/*This is for the Vertical sidebar*/ +.scrollnavbar { + height: 100%; + + .userbottom { + position: fixed; + bottom: 0px; + width: 100%; + } + + .smallCap { + padding: 3px 12px 12px 0px !important; + font-size: 0.875rem; + font-weight: 500; + margin-top: 24px; + color: rgb(var(--v-theme-textPrimary)); + + &:first-child { + margin-top: 0 !important; + } + } + + + + /*General Menu css*/ + .v-list-group__items .v-list-item, + .v-list-item { + border-radius: $border-radius-root; + padding-inline-start: calc(14px + var(--indent-padding) / 10) !important; + + margin: 0 0 2px; + + + + &:hover { + color: rgb(var(--v-theme-secondary)); + + } + + + + .v-list-item__prepend { + margin-inline-end: 13px; + } + + .v-list-item__append { + font-size: 0.875rem; + + .v-icon { + margin-inline-start: 13px; + } + } + + .v-list-item-title { + font-size: 0.875rem; + } + } + + .v-list-group__items { + .v-list-item { + min-height: 35px !important; + padding-inline-start: calc(12px + var(--indent-padding) / 10) !important; + + .v-list-item__prepend .dot { + height: 6px; + width: 6px; + background-color: rgb(var(--v-theme-textSecondary)); + border-radius: 50%; + margin-inline-end: 8px !important; + opacity: 0; + } + + .v-list-item-title { + font-size: 14px !important; + } + + &:hover { + color: rgb(var(--v-theme-secondary)); + + .v-list-item__prepend .dot { + background-color: rgb(var(--v-theme-secondary)); + } + } + + &.v-list-item--active { + .v-list-item__prepend .dot { + background-color: rgb(var(--v-theme-secondary)); + } + } + + } + } + + /*This is for the dropdown*/ + .v-list { + color: rgb(var(--v-theme-textPrimary)); + + >.v-list-item.v-list-item--active, + .v-list-item--active>.v-list-item__overlay { + background: rgb(var(--v-theme-secondary)); + color: white; + } + + >.v-list-group { + position: relative; + + >.v-list-item--active, + >.v-list-item--active:hover { + background: rgb(var(--v-theme-secondary)); + color: white; + } + + .v-list-group__items .v-list-item.v-list-item--active, + .v-list-group__items .v-list-item.v-list-item--active>.v-list-item__overlay { + background: transparent; + color: rgb(var(--v-theme-secondary)); + } + } + } +} + +.v-navigation-drawer--rail { + + .scrollnavbar .v-list .v-list-group__items, + .hide-menu { + opacity: 1; + } + + .leftPadding { + margin-left: 0px; + } +} + +@media only screen and (min-width: 1170px) { + .mini-sidebar { + .logo { + width: 40px; + overflow: hidden; + padding-left: 0; + } + + .profile-logout { + opacity: 0; + width: 0; + } + + .scrollnavbar { + .smallCap { + padding: 3px 12px 12px 12px !important; + } + } + + .leftSidebar .v-list--density-default .v-list-subheader { + padding-inline-start: 15px !important; + } + + .mini-icon { + display: block; + } + + .sidebarchip.hide-menu { + opacity: 0; + } + + .mini-text { + display: none; + } + + .v-list { + padding: 14px !important; + } + + .v-list-group__items { + .iconClass { + position: relative; + left: -2px; + } + } + + .leftSidebar:hover { + box-shadow: $box-shadow !important; + + .mini-icon { + display: none; + } + + .sidebarchip.hide-menu { + opacity: 1; + } + + .mini-text { + display: block; + } + + .profile-logout { + opacity: 1; + width: auto; + } + + .scrollnavbar { + .smallCap { + padding: 3px 12px 12px 0px !important; + } + } + + .v-list-group__items { + .iconClass { + position: relative; + left: 0px; + } + } + + .v-list--density-default .v-list-subheader { + padding-inline-start: 0px !important; + } + .v-list-group__items { + .v-list-item { + .v-list-item__prepend .dot { + opacity:0; + } + } + } + } + + .v-navigation-drawer--expand-on-hover:hover { + .logo { + width: 100%; + } + + .v-list .v-list-group__items, + .hide-menu { + opacity: 1; + } + } + + .profile-img { + margin-left: 0; + + &::before { + left: 12px; + } + } + + .menu-toggle { + margin-left: 24px; + } + + .v-list-group__items { + .v-list-item { + .v-list-item__prepend .dot { + opacity: 1; + } + } + } + + } +} + +// scrollbar +.ps__rail-y { + z-index: 9; +} + +.profile-img { + margin-left: 14px; + + &::before { + -webkit-animation: 2.5s blow 0s linear infinite; + animation: 2.5s blow 0s linear infinite; + position: absolute; + content: ""; + width: 50px; + height: 50px; + top: 40px; + border-radius: 50%; + z-index: 0; + left: 26px; + } + + @-webkit-keyframes blow { + 0% { + box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.1); + opacity: 1; + -webkit-transform: scale3d(1, 1, 0.5); + transform: scale3d(1, 1, 0.5); + } + + 50% { + box-shadow: 0 0 0 10px rgba(0, 0, 0, 0.1); + opacity: 1; + -webkit-transform: scale3d(1, 1, 0.5); + transform: scale3d(1, 1, 0.5); + } + + 100% { + box-shadow: 0 0 0 20px rgba(0, 0, 0, 0.1); + opacity: 0; + -webkit-transform: scale3d(1, 1, 0.5); + transform: scale3d(1, 1, 0.5); + } + } + + @keyframes blow { + 0% { + box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.1); + opacity: 1; + -webkit-transform: scale3d(1, 1, 0.5); + transform: scale3d(1, 1, 0.5); + } + + 50% { + box-shadow: 0 0 0 10px rgba(0, 0, 0, 0.1); + opacity: 1; + -webkit-transform: scale3d(1, 1, 0.5); + transform: scale3d(1, 1, 0.5); + } + + 100% { + box-shadow: 0 0 0 20px rgba(0, 0, 0, 0.1); + opacity: 0; + -webkit-transform: scale3d(1, 1, 0.5); + transform: scale3d(1, 1, 0.5); + } + } +} \ No newline at end of file diff --git a/web/src/scss/layout/_text.scss b/web/src/scss/layout/_text.scss new file mode 100644 index 0000000..0c58315 --- /dev/null +++ b/web/src/scss/layout/_text.scss @@ -0,0 +1,98 @@ +$sizes: ( + 'display-1': 44px, + 'display-2': 40px, + 'display-3': 30px, + 'h1': 36px, + 'h2': 30px, + 'h3': 21px, + 'h4': 18px, + 'h5': 16px, + 'h6': 14px, + 'text-10': 10px, + 'text-12': 12px, + 'text-13': 13px, + 'text-14': 14px, + 'text-15': 15px, + 'text-16': 16px, + 'text-17': 17px, + 'text-18': 18px, + 'text-20': 20px, + 'text-22': 22px, + 'text-24': 24px, + 'text-28': 28px, + 'text-34': 34px, + 'text-40': 40px, + 'text-44': 44px, + 'text-48': 48px, + 'text-50': 50px, + 'text-52': 52px, + 'text-56': 56px, + 'text-64': 64px, + 'body-text-1': 10px +); + +@each $pixel, $size in $sizes { + .#{$pixel} { + font-size: $size; + line-height: $size + 10; + } +} + +$height: ( + 'h-10': 10px, + 'h-12': 12px, + 'h-15': 15px, +); + +@each $pixel, $size in $height { + .#{$pixel} { + height: $size; + width: $size ; + } +} + +.textSecondary { + color: rgb(var(--v-theme-textSecondary)) !important; +} + +.textPrimary { + color: rgb(var(--v-theme-textPrimary)) !important; +} + +// line height + +.lh-md { + line-height: 1.57; +} +.lh-normal{ + line-height: normal; +} + +.font-weight-semibold { + font-weight: 600; +} + +// hover text +.text-hover-primary { + color: rgb(var(--v-theme-textPrimary)); + + &:hover { + color: rgb(var(--v-theme-primary)); + } +} + +.link { + color: rgb(var(--v-theme-textSecondary)); + text-decoration: none; + + &:hover { + color: rgb(var(--v-theme-primary)); + } +} + +.hover-primary { + &:hover { + color: rgb(var(--v-theme-primary)) !important; + opacity: 1; + } +} \ No newline at end of file diff --git a/web/src/scss/layout/_topbar.scss b/web/src/scss/layout/_topbar.scss new file mode 100644 index 0000000..762e4f2 --- /dev/null +++ b/web/src/scss/layout/_topbar.scss @@ -0,0 +1,110 @@ +.v-app-bar { + .v-toolbar__content { + padding: 0 15px; + > .v-btn:first-child { + margin-inline-start: 0; + } + .v-btn { + color: rgba(var(--v-theme-textsurface)) !important; + } + } +} + +.custom-text-primary { + &.v-list-item:hover > .v-list-item__overlay { + display: none; + } + .custom-title { + color: rgb(var(--v-theme-textPrimary)) !important; + } + &:hover { + .custom-title { + color: rgb(var(--v-theme-primary)) !important; + } + } +} +@media screen and (max-width:1279px) { + .mini-sidebar { + .v-navigation-drawer.v-navigation-drawer--left { + width: 270px !important; + } + } +} + + +.notify { + position: relative; + top: -20px; + right: -8px; + + .heartbit { + position: absolute; + top: -5px; + right: -2px; + height: 18px; + width: 18px; + z-index: 10; + border: 2px solid rgb(var(--v-theme-error)); + border-radius: 70px; + animation: heartbit 1s ease-out; + -moz-animation: heartbit 1s ease-out; + -moz-animation-iteration-count: infinite; + -o-animation: heartbit 1s ease-out; + -o-animation-iteration-count: infinite; + -webkit-animation: heartbit 1s ease-out; + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; + } + + .point { + width: 4px; + height: 4px; + border-radius: 30px; + position: absolute; + right: 5px; + top: 2px; + background-color: rgb(var(--v-theme-error)); + position: absolute; + } + } + + @keyframes heartbit { + 0% { + transform: scale(0); + opacity: 0; + } + + 25% { + transform: scale(0.1); + opacity: 0.1; + } + + 50% { + transform: scale(0.5); + opacity: 0.3; + } + + 75% { + transform: scale(0.8); + opacity: 0.5; + } + + 100% { + transform: scale(1); + opacity: 0; + } + } + + .v-menu.mobile_popup .v-overlay__content { + width: 100%; +} + + @media (max-width: 1199px) { + .main-head{ + &.v-app-bar .v-toolbar__content{ + width: 100%; + justify-content: space-between; + padding: 0 10px; + } + } +} \ No newline at end of file diff --git a/web/src/scss/pages/_apps.scss b/web/src/scss/pages/_apps.scss new file mode 100644 index 0000000..7825ec5 --- /dev/null +++ b/web/src/scss/pages/_apps.scss @@ -0,0 +1,240 @@ +// +// common +// + +.inside-left-sidebar { + .left-part { + width: 240px; + } +} + +// +//Full Calendar +.fc { + .fc-button-group { + >.fc-button { + display: flex; + align-items: center; + padding: 7px 10px; + border: 0; + font-size: 14px; + background-color: rgba(var(--v-theme-grey200)); + color: rgba(var(--v-theme-textPrimary)); + .fc-icon{ + color: rgba(var(--v-theme-textPrimary)); + font-size: 20px; + } + &:hover,&:focus{ + background-color: rgba(var(--v-theme-textPrimary)); + color: #fff; + .fc-icon{ + color: #fff; + } + } + } + .fc-button-primary:not(:disabled):active{ + background-color: rgba(var(--v-theme-textPrimary)); + } + .fc-button-primary:not(:disabled).fc-button-active { + background-color: rgb(var(--v-theme-textPrimary)); + } + } + .fc-today-button{ + padding: 7px 20px; + border: 0; + background-color: rgba(var(--v-theme-primary)); + } + + .fc-prev-button{ + border-radius: 30px 0 0 30px; + } + .fc-toolbar-title{ + font-weight: 600; + } + .fc-event-title{ + font-size: 14px; + padding: 3px 6px; + } + + .fc-button { + font-size: 14px; + font-weight: 500; + text-transform: capitalize; + + .fc-icon { + font-size: 1.5em; + vertical-align: unset; + } + } + .fc-daygrid-day-number{ + color: rgba(var(--v-theme-textSecondary)); + font-size: 15px; + } + + .fc-button-primary { + background: rgb(var(--v-theme-primary)); + border-color: rgb(var(--v-theme-primary)); + + color: #fff; + + &:hover { + background-color: rgb(var(--v-theme-primary)); + border-color: rgb(var(--v-theme-primary)); + } + + &:not(:disabled).fc-button-active { + background-color: rgb(var(--v-theme-primary)); + border-color: rgb(var(--v-theme-primary)); + + &:focus { + box-shadow: none; + } + } + + &:not(:disabled) { + &:active { + background-color: rgb(var(--v-theme-primary)); + border-color: rgb(var(--v-theme-primary)); + + &:focus { + box-shadow: none; + } + } + } + + &:disabled { + background-color: rgb(var(--v-theme-primary)); + border-color: rgb(var(--v-theme-primary)); + opacity: 1; + } + } + + .fc-col-header-cell-cushion { + display: inline-block; + padding: 10px 5px; + font-size: 14px; + font-weight: 600; + } + .fc-button-primary:not(:disabled).fc-button-active{ + background-color: rgb(var(--v-theme-textPrimary)); + } +} + +.fc-theme-standard { + td { + border: 1px solid rgba(var(--v-border-color), 1) !important; + } + + th { + border: 1px solid rgba(var(--v-border-color), 1) !important; + border-bottom: 0 !important; + background-color: rgba(var(--v-theme-grey200)); + height: 56px; + vertical-align: middle; + color: rgba(var(--v-theme-textSecondary)); + } + + .fc-scrollgrid { + border: 0 !important; + } +} + +.fc-h-event { + background-color: rgb(var(--v-theme-primary)); + border: 0; + display: block; +} + +.fc-direction-ltr { + .fc-button-group { + >.fc-button { + &:not(:last-child) { + border-bottom-left-radius: 30px; + border-top-left-radius: 30px; + } + + &:not(:first-child) { + border-bottom-right-radius: 30px; + border-top-right-radius: 30px; + margin-left: -1px; + } + } + } +} + +.fc-button-group { + .fc-dayGridMonth-button { + border-bottom-right-radius: 0px !important; + border-top-right-radius: 0px !important; + padding: 7px 20px !important; + } + + .fc-timeGridDay-button { + border-bottom-left-radius: 0px !important; + border-top-left-radius: 0px !important; + padding: 7px 20px !important; + } + + .fc-timeGridWeek-button { + border-radius: 0 !important; + padding: 7px 20px !important; + } +} + +.fc-today-button { + border-radius: 30px !important; + font-size: 14px; +} + +@media screen and (max-width:600px) { + .fc { + .fc-toolbar { + display: block; + text-align: center; + } + } + + .fc-toolbar-chunk { + .fc-toolbar-title { + margin: 15px 0; + } + } +} + +.customTab { + .v-btn { + &.v-tab-item--selected { + background-color: rgb(var(--v-theme-lightprimary)) !important; + + .icon { + background-color: rgb(var(--v-theme-primary)) !important; + color: #fff !important; + } + } + } +} + +.email-items { + padding: 0px 24px 12px; + + &.selected-email { + .email-title { + color: rgb(var(--v-theme-primary)) !important; + } + } + + &:hover { + background-color: rgb(var(--v-theme-hoverColor)); + + .email-title { + color: rgb(var(--v-theme-primary)) !important; + } + + } +} + +.email-content { + p { + margin: 10px 0; + } +} \ No newline at end of file diff --git a/web/src/scss/pages/_authentication.scss b/web/src/scss/pages/_authentication.scss new file mode 100644 index 0000000..63f6b3a --- /dev/null +++ b/web/src/scss/pages/_authentication.scss @@ -0,0 +1,90 @@ +.authentication{ + &::before{ + content: ""; + position: absolute; + height: 100%; + width: 100%; + opacity: 0.3; + left: 0; + top: 0; + bottom: 0; + background: radial-gradient(rgb(210, 241, 223), rgb(211, 215, 250), rgb(186, 216, 244)) 0% 0% / 400% 400%; + } + + @media screen and (max-width:1280px){ + .auth-header{ + position: unset; + } + } +} +.mw-450{ + max-width: 450px; + width: 100%;; +} +.auth-header{ + position: absolute; + top: 0; + left: 0; +} +.verification{ + .v-field__input{ + text-align: center; + } +} +.auth-divider{ + span{ + z-index: 1; + } + &::before{ + position: absolute; + width: 100%; + border-top: thin solid rgb(var(--v-theme-borderColor)); + top: 50%; + content: ""; + transform: translateY(50%); + left: 0; + } + &::after + { + position: absolute; + width: 100%; + border-top: thin solid rgb(var(--v-theme-borderColor)); + top: 50%; + content: ""; + transform: translateY(50%); + right: 0; + } +} + +@media (min-width: 1536px){ + .auth{ + .v-col-lg-7{ + flex: 0 0 66.66%; + max-width: 66.66%; + } + .v-col-lg-5{ + flex: 0 0 33.33%; + max-width: 33.33%; + } + } + +} +@media screen and (max-width:1280px){ + .mh-100{ + height: 100% !important; + } +} + +@media screen and (max-width:600px){ + .mw-100{ + width: 100%; + padding: 0 15px; + } +} + +.auth{ + .v-switch .v-label, .v-checkbox .v-label { + opacity: 0.7; + font-weight: 400 !important; + } +} \ No newline at end of file diff --git a/web/src/scss/pages/_dashboards.scss b/web/src/scss/pages/_dashboards.scss new file mode 100644 index 0000000..e222a21 --- /dev/null +++ b/web/src/scss/pages/_dashboards.scss @@ -0,0 +1,227 @@ +.month-table { + &.custom-px-0 { + thead { + tr { + th:first-child { + padding-left: 0 !important; + } + + th:last-child { + padding-right: 0 !important; + } + } + } + + tr.month-item { + td:first-child { + padding-left: 0 !important; + } + + td:last-child { + padding-right: 0 !important; + } + } + } + + tr.month-item { + td { + padding-top: 16px !important; + padding-bottom: 16px !important; + } + + &:hover { + background: transparent !important; + } + } + + tr.month-item-0 { + td { + padding-top: 12px !important; + padding-bottom: 12px !important; + } + + &:hover { + background: transparent !important; + } + } + + tr.month-item-hover { + + td { + padding-top: 12px !important; + padding-bottom: 12px !important; + } + + border-left: 4px solid transparent; + + &:hover { + border-left: 4px solid rgba(var(--v-theme-primary)) !important; + } + } + + +} + + +.no-line { + + .v-table .v-table__wrapper>table>tbody>tr:not(:last-child)>td { + border-bottom: 0 !important; + } + + .v-table .v-table__wrapper>table>tbody>tr>td { + padding: 12px; + } +} + +.recent-transaction { + .line { + width: 2px; + height: 35px; + } +} + + +.chip-label { + width: 80px; + justify-content: center; +} + +// +// Apex Chart +// + +body { + .apexcharts-tooltip { + border-radius: 16px; + } + + .apexcharts-tooltip-marker { + border-radius: 4px; + width: 12px; + height: 4px; + } + + .apexcharts-tooltip.apexcharts-theme-dark { + background: rgba(17, 28, 45, 0.8); + + .apexcharts-tooltip-title { + border-bottom: 0; + background: rgba(17, 28, 45, 0.7); + } + } + + .apexcharts-tooltip-series-group { + padding: 0 14px; + } + + .apexcharts-tooltip-title { + padding: 10px 14px; + } +} + +.profile-activity { + + .v-tab { + + &.v-btn { + border: 1px dashed rgba(var(--v-theme-borderColor)); + border-radius: 8px; + min-width: 90px; + overflow: hidden; + padding: 15px 20px; + overflow: hidden; + + .v-btn__content { + display: block; + } + + &.v-tab--selected{ + border: 1px solid rgba(var(--v-theme-borderColor)); + border-bottom: 2px solid; + } + .v-tab__slider{ + opacity: 0; + } + } + + } + + .v-slide-group__content { + gap: 16px; + } +} + +.comment-box { + border-bottom: 1px solid rgba(var(--v-theme-borderColor)); + padding-bottom: 20px; + margin-bottom: 20px; + cursor: pointer; + + &:last-child { + border-bottom: 0; + padding-bottom: 0px; + margin-bottom: 0px; + } + + .comment-action { + opacity: 0; + transition: 0.5s; + } + + &:hover { + .comment-action { + opacity: 1; + } + } +} + +.todo-list { + border-bottom: 1px solid rgba(var(--v-theme-borderColor)); + padding-bottom: 25px; + margin-bottom: 25px; + cursor: pointer; + + &:last-child { + border-bottom: 0; + padding-bottom: 20px; + margin-bottom: 20px; + } + +} + +.progress-cards { + border-inline-end: 1px solid rgba(var(--v-theme-borderColor)); + + &:last-child { + border: 0; + } + + @media screen and (max-width:991px) { + border-inline-end: 0 !important + } +} + +.notification { + border-bottom: 1px solid rgba(var(--v-theme-borderColor)); + + &:last-child { + border: 0; + } +} + + +.earning-cards { + @media screen and (max-width:991px){ + .w-25{ + width: 100% !important; + &.border-e{ + border: 0 !important; + } + .mobile-border{ + border-bottom:1px solid rgba(var(--v-theme-borderColor)); + padding-bottom: 8px !important; + } + } + } +} \ No newline at end of file diff --git a/web/src/scss/pages/_datatable.scss b/web/src/scss/pages/_datatable.scss new file mode 100644 index 0000000..a540915 --- /dev/null +++ b/web/src/scss/pages/_datatable.scss @@ -0,0 +1,48 @@ +@use "../variables" as *; + +.customize-table { + border-radius: $border-radius-root; + .vue3-easy-data-table__main, + .vue3-easy-data-table__footer { + border-radius: $border-radius-root; + } + white-space: nowrap; + --easy-table-border: 1px solid rgb(var(--v-theme-inputBorder), 0.1); + --easy-table-row-border: 1px solid rgb(var(--v-theme-inputBorder), 0.1); + + --easy-table-header-font-size: 14px; + --easy-table-header-height: 50px; + --easy-table-header-font-color: rgb(var(--v-theme-textPrimary)); + --easy-table-header-background-color: rgb(var(--v-theme-surface)); + + --easy-table-header-item-padding: 10px 15px; + + --easy-table-body-even-row-font-color: rgb(var(--v-theme-surface)); + --easy-table-body-even-row-background-color: rgba(0, 0, 0, 0.02); + + --easy-table-body-row-font-color: rgb(var(--v-theme-textPrimary)); + --easy-table-body-row-background-color: rgb(var(--v-theme-surface)); + --easy-table-body-row-height: 50px; + --easy-table-body-row-font-size: 14px; + + --easy-table-body-row-hover-font-color: rgb(var(--v-theme-textPrimary)); + --easy-table-body-row-hover-background-color: rgba(0, 0, 0, 0.02); + + --easy-table-body-item-padding: 15px; + + --easy-table-footer-background-color: rgb(var(--v-theme-surface)); + --easy-table-footer-font-color: rgb(var(--v-theme-textPrimary)); + --easy-table-footer-font-size: 14px; + --easy-table-footer-padding: 0px 10px; + --easy-table-footer-height: 50px; + + --easy-table-rows-per-page-selector-width: 70px; + --easy-table-rows-per-page-selector-option-padding: 10px; + + --easy-table-scrollbar-track-color: #; + --easy-table-scrollbar-color: #; + --easy-table-scrollbar-thumb-color: #4c5d7a; + --easy-table-scrollbar-corner-color: #; + + --easy-table-loading-mask-background-color: #; +} diff --git a/web/src/scss/pages/_editor.scss b/web/src/scss/pages/_editor.scss new file mode 100644 index 0000000..0076bfc --- /dev/null +++ b/web/src/scss/pages/_editor.scss @@ -0,0 +1,61 @@ +.ProseMirror { + padding: 20px; + border: 1px solid rgb(var(--v-theme-inputBorder), 0.3); + border-radius: 0 0 12px 12px; + &.ProseMirror-focused { + outline-color: rgb(var(--v-theme-primary), 0.3) !important; + } + > * + * { + margin-top: 0.75em; + } + + ul, + ol { + padding: 0 1rem; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + line-height: 1.1; + } + + code { + background-color: rgba(#616161, 0.1); + color: #616161; + } + + pre { + background: #0d0d0d; + color: #fff; + font-family: 'JetBrainsMono', monospace; + padding: 0.75rem 1rem; + border-radius: 0.5rem; + + code { + color: inherit; + padding: 0; + background: none; + font-size: 0.8rem; + } + } + + img { + max-width: 100%; + height: auto; + } + + blockquote { + padding-left: 1rem; + border-left: 2px solid rgba(#0d0d0d, 0.1); + } + + hr { + border: none; + border-top: 2px solid rgba(#0d0d0d, 0.1); + margin: 2rem 0; + } +} diff --git a/web/src/scss/style.scss b/web/src/scss/style.scss new file mode 100644 index 0000000..7edb098 --- /dev/null +++ b/web/src/scss/style.scss @@ -0,0 +1,91 @@ +@use "./variables"; +@use "vuetify/styles.scss"; +@use "./override"; +@use "./layout/text"; +@use "./layout/reboot"; +@use "./layout/container"; +@use "./layout/sidebar"; +@use "./layout/rtl"; +@use "./layout/topbar"; +@use "./layout/horizontal"; +@use "./layout/dark"; +@use "./layout/customizer"; + +@use "./theme/themeColors"; + +@use "./components/VDatatable"; +@use "./components/VAlert"; +@use "./components/VButtons"; +@use "./components/VBreadcrumb"; +@use "./components/VCard"; +@use "./components/VCarousel"; +@use "./components/VField"; +@use "./components/VList"; +@use "./components/VInput"; +@use "./components/VNavigationDrawer"; +@use "./components/VShadow"; +@use "./components/VSwitch"; +@use "./components/VSelectionControl"; +@use "./components/VTextField"; +@use "./components/VTextarea"; +@use "./components/VTabs"; +@use "./components/VTable"; +@use "./components/VExpansionpanel"; +@use "./components/VDatatables"; +@use "./components/VStepper"; +@use "./components/VLabs"; + +@use "./front/header"; +@use "./front/general"; + +@use "./pages/datatable"; +@use "./pages/dashboards"; +@use "./pages/editor"; +@use "./pages/authentication"; +@use "./pages/apps"; + +//@use 'vue3-perfect-scrollbar/dist/vue3-perfect-scrollbar.css'; + +.small-stat-card { + border: 1px solid #25334355; + background-color: #152332; + border-radius: 8px; +} + +.small-stat-card .v-card-subtitle { + padding: 8px 14px 4px; + font-size: 13px; + border-bottom: 1px solid #253343; +} +.small-stat-card .v-card-text { + font-size: 20px; + padding: 9px 14px 10px; +} + +.small-stat-card.bg-error { + background-color: #cf667925 !important; + border: 1px solid #cf667955 !important; +} +.small-stat-card.bg-error .v-card-subtitle { + border-bottom: 1px solid #cf667955; +} + +.small-stat-card.bg-warning { + background-color: #fb8c0025 !important; + border: 1px solid #fb8c0055 !important; +} +.small-stat-card.bg-warning .v-card-subtitle { + border-bottom: 1px solid #fb8c0055; +} +.small-stat-card .text-warning { + color: #fb8c00de !important; +} + +.row-clickable tbody tr { + cursor: pointer; +} + +.v-data-table-rows-loading, +.v-data-table-rows-no-data { + text-align: left; +} diff --git a/web/src/scss/theme/_themeColors.scss b/web/src/scss/theme/_themeColors.scss new file mode 100644 index 0000000..6e3c2ae --- /dev/null +++ b/web/src/scss/theme/_themeColors.scss @@ -0,0 +1,59 @@ +// Light Theme Colors +.v-theme--AQUA_THEME { + --v-theme-primary: 0, 116, 186; + --v-theme-secondary: 71, 215, 188; + --v-theme-lightprimary: 229, 241, 248; + --v-theme-lightsecondary: 237, 251, 247; +} + +.v-theme--PURPLE_THEME { + --v-theme-primary: 118, 62, 189; + --v-theme-secondary: 148, 208, 214; + --v-theme-lightprimary: 242, 236, 249; + --v-theme-lightsecondary: 237, 248, 250; +} + +.v-theme--GREEN_THEME { + --v-theme-primary: 10, 126, 164; + --v-theme-secondary: 204, 218, 78; + --v-theme-lightprimary: 230, 242, 246; + --v-theme-lightsecondary: 250, 251, 239; +} + +.v-theme--CYAN_THEME { + --v-theme-primary: 1, 192, 200; + --v-theme-secondary: 251, 150, 120; + --v-theme-lightprimary: 235, 249, 250; + --v-theme-lightsecondary: 255, 245, 242; +} + +.v-theme--ORANGE_THEME { + --v-theme-primary: 250, 137, 107; + --v-theme-secondary: 0, 116, 186; + --v-theme-lightprimary: 251, 242, 239; + --v-theme-lightsecondary: 239, 249, 255; +} + +.v-theme--DARK_AQUA_THEME{ + --v-theme-primary: 0, 116, 186; + --v-theme-secondary: 71, 215, 188; +} + +.v-theme--DARK_PURPLE_THEME { + --v-theme-primary: 118, 62, 189; + --v-theme-secondary: 148, 208, 214; +} + +.v-theme--DARK_GREEN_THEME { + --v-theme-primary: 10, 126, 164; + --v-theme-secondary: 204, 218, 78; +} + +.v-theme--DARK_CYAN_THEME { + --v-theme-primary: 1, 192, 200; + --v-theme-secondary: 251, 150, 120; +} +.v-theme--DARK_ORANGE_THEME { + --v-theme-primary: 250, 137, 107; + --v-theme-secondary: 0, 116, 186; +} \ No newline at end of file diff --git a/web/src/theme/DarkTheme.ts b/web/src/theme/DarkTheme.ts new file mode 100644 index 0000000..16fed7f --- /dev/null +++ b/web/src/theme/DarkTheme.ts @@ -0,0 +1,191 @@ +import type { ThemeTypes } from "./ThemeTypes" + +const DARK_BLUE_THEME: ThemeTypes = { + name: "DARK_BLUE_THEME", + dark: true, + variables: { + "border-color": "#333F55", + "border-opacity": 1, + }, + colors: { + primary: "#1B84FF", + secondary: "#0cb9c5", + lightprimary: "#253662", + lightsecondary: "#1C455D", + lightsuccess: "#1B3C48", + lighterror: "#4B313D", + lightwarning: "#4D3A2A", + lightinfo: "#223662", + textPrimary: "#EAEFF4", + textSecondary: "#7C8FAC", + borderColor: "#333F55", + inputBorder: "#465670", + containerBg: "#2a3447", + background: "#192838", + surface: "#152332", + hoverColor: "#333f55", + "on-surface-variant": "#2a3447", + grey100: "#333F55", + grey200: "#465670", + }, +} + +const DARK_AQUA_THEME: ThemeTypes = { + name: "DARK_AQUA_THEME", + dark: true, + variables: { + "border-color": "#333F55", + "border-opacity": 1, + }, + colors: { + primary: "#0074BA", + info: "#725af2", + secondary: "#47D7BC", + lightprimary: "#103247", + lightsecondary: "#0C4339", + lightsuccess: "#1B3C48", + lighterror: "#4B313D", + lightwarning: "#4D3A2A", + lightinfo: "#223662", + textPrimary: "#EAEFF4", + textSecondary: "#7C8FAC", + borderColor: "#333F55", + inputBorder: "#465670", + containerBg: "#171c23", + background: "#192838", + surface: "#152332", + hoverColor: "#333f55", + "on-surface-variant": "#171c23", + grey100: "#333F55", + grey200: "#465670", + }, +} + +const DARK_PURPLE_THEME: ThemeTypes = { + name: "DARK_PURPLE_THEME", + dark: true, + variables: { + "border-color": "#333F55", + "border-opacity": 1, + }, + colors: { + primary: "#763EBD", + secondary: "#95CFD5", + lightprimary: "#26153C", + lightsecondary: "#09454B", + lightsuccess: "#1B3C48", + lighterror: "#4B313D", + lightwarning: "#4D3A2A", + lightinfo: "#223662", + textPrimary: "#EAEFF4", + textSecondary: "#7C8FAC", + borderColor: "#333F55", + inputBorder: "#465670", + containerBg: "#171c23", + background: "#192838", + surface: "#152332", + hoverColor: "#333f55", + "on-surface-variant": "#171c23", + grey100: "#333F55", + grey200: "#465670", + }, +} + +const DARK_GREEN_THEME: ThemeTypes = { + name: "DARK_GREEN_THEME", + dark: true, + variables: { + "border-color": "#333F55", + "border-opacity": 1, + }, + colors: { + primary: "#0A7EA4", + secondary: "#CCDA4E", + lightprimary: "#05313F", + lightsecondary: "#282917", + lightsuccess: "#1B3C48", + lighterror: "#4B313D", + lightwarning: "#4D3A2A", + lightinfo: "#223662", + textPrimary: "#EAEFF4", + textSecondary: "#7C8FAC", + borderColor: "#333F55", + inputBorder: "#465670", + containerBg: "#171c23", + background: "#192838", + surface: "#152332", + hoverColor: "#333f55", + "on-surface-variant": "#171c23", + grey100: "#333F55", + grey200: "#465670", + }, +} + +const DARK_CYAN_THEME: ThemeTypes = { + name: "DARK_CYAN_THEME", + dark: true, + variables: { + "border-color": "#333F55", + "border-opacity": 1, + }, + colors: { + primary: "#01C0C8", + secondary: "#FB9678", + lightprimary: "#003638", + lightsecondary: "#40241C", + lightsuccess: "#1B3C48", + lighterror: "#4B313D", + lightwarning: "#4D3A2A", + lightinfo: "#223662", + textPrimary: "#EAEFF4", + textSecondary: "#7C8FAC", + borderColor: "#333F55", + inputBorder: "#465670", + containerBg: "#171c23", + background: "#192838", + surface: "#152332", + hoverColor: "#333f55", + "on-surface-variant": "#171c23", + grey100: "#333F55", + grey200: "#465670", + }, +} + +const DARK_ORANGE_THEME: ThemeTypes = { + name: "DARK_ORANGE_THEME", + dark: true, + variables: { + "border-color": "#333F55", + "border-opacity": 1, + }, + colors: { + primary: "#FA896B", + secondary: "#0074BA", + lightprimary: "#402E32", + lightsecondary: "#082E45", + lightsuccess: "#1B3C48", + lighterror: "#4B313D", + lightwarning: "#4D3A2A", + lightinfo: "#223662", + textPrimary: "#EAEFF4", + textSecondary: "#7C8FAC", + borderColor: "#333F55", + inputBorder: "#465670", + containerBg: "#171c23", + background: "#192838", + surface: "#152332", + hoverColor: "#333f55", + "on-surface-variant": "#171c23", + grey100: "#333F55", + grey200: "#465670", + }, +} + +export { + DARK_BLUE_THEME, + DARK_AQUA_THEME, + DARK_ORANGE_THEME, + DARK_PURPLE_THEME, + DARK_GREEN_THEME, + DARK_CYAN_THEME, +} diff --git a/web/src/theme/LightTheme.ts b/web/src/theme/LightTheme.ts new file mode 100644 index 0000000..28231e7 --- /dev/null +++ b/web/src/theme/LightTheme.ts @@ -0,0 +1,40 @@ +import type { ThemeTypes } from "./ThemeTypes" + +const BLUE_THEME: ThemeTypes = { + name: "BLUE_THEME", + dark: false, + variables: { + "border-color": "#ebf1f6", + "border-opacity": 1, + }, + colors: { + primary: "#1B84FF", + secondary: "#43CED7", + info: "#2CABE3", + success: "#2CD07E", + accent: "#FFAB91", + warning: "#F6C000", + error: "#F8285A", + purple: "#725AF2", + indigo: "#6610f2", + lightprimary: "#EDF5FD", + lightsecondary: "#F2FCFC", + lightsuccess: "#EDFDF2", + lighterror: "#FFF0F4", + lightwarning: "#FFFCF0", + lightinfo: "#E4F5FF", + textPrimary: "#3A4752", + textSecondary: "#768B9E", + borderColor: "#ebf1f6", + inputBorder: "#DFE5EF", + containerBg: "#ffffff", + background: "#eef5f9", + hoverColor: "#f6f9fc", + surface: "#fff", + "on-surface-variant": "#fff", + grey100: "#F2F6FA", + grey200: "#EAEFF4", + }, +} + +export { BLUE_THEME } diff --git a/web/src/theme/ThemeTypes.ts b/web/src/theme/ThemeTypes.ts new file mode 100644 index 0000000..b7a3c68 --- /dev/null +++ b/web/src/theme/ThemeTypes.ts @@ -0,0 +1,33 @@ +export type ThemeTypes = { + name: string + dark: boolean + variables?: object + colors: { + primary?: string + secondary?: string + info?: string + success?: string + accent?: string + warning?: string + error?: string + purple?: string + indigo?: string + lightprimary?: string + lightsecondary?: string + lightsuccess?: string + lighterror?: string + lightinfo?: string + lightwarning?: string + textPrimary?: string + textSecondary?: string + borderColor?: string + hoverColor?: string + inputBorder?: string + containerBg?: string + background?: string + surface?: string + "on-surface-variant"?: string + grey100?: string + grey200?: string + } +} diff --git a/web/src/use/use-breadcrumbs.ts b/web/src/use/use-breadcrumbs.ts new file mode 100644 index 0000000..36b12e0 --- /dev/null +++ b/web/src/use/use-breadcrumbs.ts @@ -0,0 +1,95 @@ +import { reactive, toRefs, watch, MaybeRefOrGetter, toValue } from "vue" +import { RouteLocationRaw } from "vue-router" +import { cloneDeep, isUndefined } from "lodash" + +export type Breadcrumb = { + title?: string + disabled?: boolean + exact?: boolean + to: RouteLocationRaw +} + +// Global state for breadcrumbs +const state = reactive<{ + title?: string + baseCrumb: Breadcrumb + breadcrumbs: Breadcrumb[] + showBackButton: boolean +}>({ + title: "Default", + baseCrumb: { + title: "Dashboard", + to: { + name: "DashboardPage", + }, + }, + breadcrumbs: [], + showBackButton: false, +}) + +// TODO: Consider supporting config option for setting base crumb? +export function useBreadcrumbs( + title?: MaybeRefOrGetter, + breadcrumbs?: MaybeRefOrGetter, + options?: MaybeRefOrGetter<{ + baseCrumb?: Breadcrumb + }>, + isGrounded?: MaybeRefOrGetter, + showBackButton?: MaybeRefOrGetter +) { + watch( + () => toValue(title), + (newTitle) => { + if (isUndefined(newTitle)) return + + state.title = newTitle + }, + { + immediate: true, + } + ) + + watch( + () => toValue(showBackButton), + (newShowBackButton) => { + state.showBackButton = newShowBackButton ?? false + }, + { + immediate: true, + } + ) + + watch( + () => cloneDeep(toValue(breadcrumbs)), + (newBreadcrumbs) => { + if (isUndefined(newBreadcrumbs)) return + + state.breadcrumbs = [state.baseCrumb, ...newBreadcrumbs] + }, + { + immediate: true, + deep: true, + } + ) + + watch( + () => cloneDeep(toValue(options)), + (newOptions) => { + if (isUndefined(newOptions)) return + + if (!isUndefined(newOptions.baseCrumb)) { + state.baseCrumb = newOptions.baseCrumb + } + }, + { + immediate: true, + deep: true, + } + ) + + return { + ...toRefs(state), + } +} + +export default useBreadcrumbs diff --git a/web/src/use/use-current-user.ts b/web/src/use/use-current-user.ts new file mode 100644 index 0000000..cf8597e --- /dev/null +++ b/web/src/use/use-current-user.ts @@ -0,0 +1,74 @@ +import { computed, reactive, toRefs } from "vue" +import { DateTime } from "luxon" + +import currentUserApi, { UserRoles, type UserAsShow } from "@/api/current-user-api" + +export { UserRoles, type UserAsShow } + +// TODO: consider sending this with every api request? +export const CURRENT_USERS_TIMEZONE = DateTime.local().zoneName + +// Global state +const state = reactive<{ + currentUser: UserAsShow | null + isLoading: boolean + isErrored: boolean + isCached: boolean +}>({ + currentUser: null, + isLoading: false, + isErrored: false, + isCached: false, +}) + +type State = typeof state +type LoadedState = Omit & { + currentUser: Exclude +} + +export function useCurrentUser() { + type StateOrLoadedState = IsLoaded extends true ? LoadedState : State + + const isReady = computed(() => state.isCached && !state.isLoading && !state.isErrored) + + const isSystemAdmin = computed(() => { + return state.currentUser?.roles.includes(UserRoles.SYSTEM_ADMIN) + }) + + async function fetch(): Promise { + state.isLoading = true + try { + const { user } = await currentUserApi.get() + state.isErrored = false + state.currentUser = user + state.isCached = true + return user + } catch (error) { + console.error("Failed to fetch current user:", error) + state.isErrored = true + throw error + } finally { + state.isLoading = false + } + } + + // Needs to be called during logout or current user will persist. + function reset() { + state.currentUser = null + state.isLoading = false + state.isErrored = false + state.isCached = false + } + + return { + ...toRefs(state as StateOrLoadedState), + isReady, + fetch, + refresh: fetch, + reset, + // helpers + isSystemAdmin, + } +} + +export default useCurrentUser diff --git a/web/src/use/use-interface.ts b/web/src/use/use-interface.ts new file mode 100644 index 0000000..17f169f --- /dev/null +++ b/web/src/use/use-interface.ts @@ -0,0 +1,46 @@ +import { reactive, toRefs, watch } from "vue" + +// Global state for breadcrumbs +const state = reactive<{ + sidebarDrawer: boolean + sidebarMini: boolean +}>({ + sidebarDrawer: false, + sidebarMini: false, +}) + +/* watch( + state, + (newValue) => { + console.log("Sidebar drawer state changed:", newValue) + }, + { deep: true } +) */ + +export function useInterface() { + function setSidebarDrawer(value: boolean) { + state.sidebarDrawer = value + } + + function setSidebarMini(value: boolean) { + state.sidebarMini = value + } + + function toggleSidebarDrawer() { + state.sidebarDrawer = !state.sidebarDrawer + } + + function toggleSidebarMini() { + state.sidebarMini = !state.sidebarMini + } + + return { + ...toRefs(state), + setSidebarDrawer, + setSidebarMini, + toggleSidebarDrawer, + toggleSidebarMini, + } +} + +export default useInterface diff --git a/web/src/use/use-notifications.ts b/web/src/use/use-notifications.ts new file mode 100644 index 0000000..78f9f5c --- /dev/null +++ b/web/src/use/use-notifications.ts @@ -0,0 +1,71 @@ +import { type Ref, reactive, toRefs, ref, unref, watch } from "vue" + +import notificationsApi, { + NotificationSourceTypes, + type Notification, + type NotificationWhereOptions, + type NotificationFiltersOptions, +} from "@/api/notifications-api" + +export { + NotificationSourceTypes, + type Notification, + type NotificationWhereOptions, + type NotificationFiltersOptions, +} + +export function useNotifications( + queryOptions: Ref<{ + where?: Record + page?: number + perPage?: number + }> = ref({}), + { skipWatchIf = () => false }: { skipWatchIf?: () => boolean } = {} +) { + const state = reactive<{ + notifications: Notification[] + totalCount: number + isLoading: boolean + isErrored: boolean + }>({ + notifications: [], + totalCount: 0, + isLoading: false, + isErrored: false, + }) + + async function fetch(): Promise { + state.isLoading = true + try { + const { notifications, totalCount } = await notificationsApi.list(unref(queryOptions)) + state.isErrored = false + state.notifications = notifications + state.totalCount = totalCount + return notifications + } catch (error) { + console.error("Failed to fetch notifications:", error) + state.isErrored = true + throw error + } finally { + state.isLoading = false + } + } + + watch( + () => unref(queryOptions), + async () => { + if (skipWatchIf()) return + + await fetch() + }, + { deep: true, immediate: true } + ) + + return { + ...toRefs(state), + fetch, + refresh: fetch, + } +} + +export default useNotifications diff --git a/web/src/use/use-snack.ts b/web/src/use/use-snack.ts new file mode 100644 index 0000000..fa319b7 --- /dev/null +++ b/web/src/use/use-snack.ts @@ -0,0 +1,69 @@ +import { reactive, toRef, ToRef } from "vue" +import { VSnackbar } from "vuetify/components" + +type VSnackbarProps = VSnackbar["$props"] + +const state = reactive<{ + message: string + options: VSnackbarProps +}>({ + message: "", + options: {}, +}) + +/** + * @example + * const snack = useSnack() + * snack("Hello world", { color: "success" }) + * + * @example + * const snack = useSnack() + * snack.success("Hello world") + */ +export function useSnack(defaultOptions: VSnackbarProps = {}): { + (message: string, options?: VSnackbarProps): void + message: ToRef + options: ToRef + notify: (message: string, options?: VSnackbarProps) => void + reset: () => void + success: (message: string, options?: VSnackbarProps) => void + error: (message: string, options?: VSnackbarProps) => void + info: (message: string, options?: VSnackbarProps) => void + warning: (message: string, options?: VSnackbarProps) => void +} { + const notify = (message: string, options: VSnackbarProps = {}) => { + state.message = message + state.options = { ...defaultOptions, ...options } + } + + notify.message = toRef(state, "message") + notify.options = toRef(state, "options") + + notify.notify = notify + + notify.reset = () => { + state.message = "" + state.options = {} + } + + notify.success = (message: string, options: VSnackbarProps = {}) => { + notify(message, { color: "success", ...options }) + } + + notify.error = (message: string, options: VSnackbarProps = {}) => { + notify(message, { color: "error", ...options }) + } + + notify.info = (message: string, options: VSnackbarProps = {}) => { + notify(message, { color: "info", ...options }) + } + + notify.warning = (message: string, options: VSnackbarProps = {}) => { + notify(message, { color: "warning", ...options }) + } + + // @ts-expect-error - VSnackbarProps definition is probably not infinte + return notify +} + +export default useSnack diff --git a/web/src/use/use-status.ts b/web/src/use/use-status.ts new file mode 100644 index 0000000..ee5ff89 --- /dev/null +++ b/web/src/use/use-status.ts @@ -0,0 +1,47 @@ +import { reactive, toRefs } from "vue" + +import statusApi, { type Status } from "@/api/status-api" + +export { type Status } + +export function useStatus() { + const state = reactive<{ + releaseTag: null | string + gitCommitHash: null | string + isLoading: boolean + isErrored: boolean + }>({ + releaseTag: null, + gitCommitHash: null, + isLoading: false, + isErrored: false, + }) + + async function fetch(): Promise { + state.isLoading = true + try { + const status = await statusApi.get() + state.isErrored = false + state.releaseTag = status.RELEASE_TAG + state.gitCommitHash = status.GIT_COMMIT_HASH + return status + } catch (error) { + console.error("Failed to fetch status:", error) + state.isErrored = true + throw error + } finally { + state.isLoading = false + } + } + + // Fetch status immediately + fetch() + + return { + ...toRefs(state), + fetch, + refresh: fetch, + } +} + +export default useStatus diff --git a/web/src/use/use-user.ts b/web/src/use/use-user.ts new file mode 100644 index 0000000..76effb3 --- /dev/null +++ b/web/src/use/use-user.ts @@ -0,0 +1,113 @@ +import { type Ref, reactive, toRefs, unref, watch } from "vue" +import { isNil } from "lodash" + +import { type Policy } from "@/api/base-api" +import usersApi, { type User } from "@/api/users-api" + +export { type User } + +export function useUser(id: Ref) { + const state = reactive<{ + user: User | null + policy: Policy | null + isLoading: boolean + isErrored: boolean + }>({ + user: null, + policy: null, + isLoading: false, + isErrored: false, + }) + + async function fetch(): Promise { + const staticId = unref(id) + if (isNil(staticId)) { + throw new Error("id is required") + } + + state.isLoading = true + try { + const { user, policy } = await usersApi.get(staticId) + state.isErrored = false + state.user = user + state.policy = policy + return user + } catch (error) { + console.error("Failed to fetch user:", error) + state.isErrored = true + throw error + } finally { + state.isLoading = false + } + } + + async function save(): Promise { + const staticId = unref(id) + if (isNil(staticId)) { + throw new Error("id is required") + } + + if (isNil(state.user)) { + throw new Error("No user to save") + } + + state.isLoading = true + try { + const { user } = await usersApi.update(staticId, state.user) + state.isErrored = false + state.user = user + return user + } catch (error) { + console.error("Failed to save user:", error) + state.isErrored = true + throw error + } finally { + state.isLoading = false + } + } + + async function directorySync() { + const staticId = unref(id) + if (isNil(staticId)) { + throw new Error("id is required") + } + + if (isNil(state.user)) { + throw new Error("No user to save") + } + + state.isLoading = true + try { + const { user } = await usersApi.directorySync(staticId) + state.isErrored = false + state.user = user + return user + } catch (error) { + console.error("Failed to sync user:", error) + state.isErrored = true + throw error + } finally { + state.isLoading = false + } + } + + watch( + () => unref(id), + async (newId) => { + if (isNil(newId)) return + + await fetch() + }, + { immediate: true } + ) + + return { + ...toRefs(state), + fetch, + refresh: fetch, + save, + directorySync, + } +} + +export default useUser diff --git a/web/src/use/use-users.ts b/web/src/use/use-users.ts new file mode 100644 index 0000000..8660299 --- /dev/null +++ b/web/src/use/use-users.ts @@ -0,0 +1,62 @@ +import { type Ref, reactive, toRefs, ref, unref, watch } from "vue" + +import usersApi, { + type User, + type UserWhereOptions, + type UserFiltersOptions, + type UserQueryOptions, +} from "@/api/users-api" + +export { type User, type UserWhereOptions, type UserFiltersOptions, type UserQueryOptions } + +export function useUsers( + queryOptions: Ref = ref({}), + { skipWatchIf = () => false }: { skipWatchIf?: () => boolean } = {} +) { + const state = reactive<{ + users: User[] + totalCount: number + isLoading: boolean + isErrored: boolean + }>({ + users: [], + totalCount: 0, + isLoading: false, + isErrored: false, + }) + + async function fetch(): Promise { + state.isLoading = true + try { + const { users, totalCount } = await usersApi.list(unref(queryOptions)) + state.isErrored = false + state.users = users + state.totalCount = totalCount + return users + } catch (error) { + console.error("Failed to fetch users:", error) + state.isErrored = true + throw error + } finally { + state.isLoading = false + } + } + + watch( + () => [skipWatchIf(), unref(queryOptions)], + async ([skip]) => { + if (skip) return + + await fetch() + }, + { deep: true, immediate: true } + ) + + return { + ...toRefs(state), + fetch, + refresh: fetch, + } +} + +export default useUsers diff --git a/web/src/use/utils/use-page-title.ts b/web/src/use/utils/use-page-title.ts new file mode 100644 index 0000000..ab469b8 --- /dev/null +++ b/web/src/use/utils/use-page-title.ts @@ -0,0 +1,22 @@ +import { MaybeRefOrGetter, ref, toValue, watch } from "vue" +import { isNil, isEmpty } from "lodash" + +import { APPLICATION_NAME } from "@/config" + +export function usePageTitle(title: MaybeRefOrGetter = ref(null)) { + watch( + () => toValue(title), + (newTitle) => { + if (!isNil(newTitle) && !isEmpty(newTitle)) { + document.title = `${APPLICATION_NAME} - ${newTitle}` + } else { + document.title = APPLICATION_NAME + } + }, + { + immediate: true, + } + ) +} + +export default usePageTitle diff --git a/web/src/use/utils/use-route-query-enhanced.ts b/web/src/use/utils/use-route-query-enhanced.ts new file mode 100644 index 0000000..13981aa --- /dev/null +++ b/web/src/use/utils/use-route-query-enhanced.ts @@ -0,0 +1,32 @@ +import { computed, MaybeRefOrGetter, Ref } from "vue" +import { useRouteQuery } from "@vueuse/router" +import { RouteParamValueRaw } from "vue-router" + +type RouteQueryValueRaw = RouteParamValueRaw | string[] + +/** + * Enhanced useRouteQuery with parse and stringify transformations + * + * See https://github.com/vueuse/vueuse/blob/0f11df11962f5f2e912d66c8544bc6767630780a/packages/router/useRouteQuery/index.ts + */ +export function useRouteQueryEnhanced( + name: string, + defaultValue: MaybeRefOrGetter, + options: { + parse: (value: T) => K + stringify: (value: K) => T + } +): Ref { + const query = useRouteQuery(name, defaultValue) + + return computed({ + get() { + return options.parse(query.value) + }, + set(value: K) { + query.value = options.stringify(value) + }, + }) +} + +export default useRouteQueryEnhanced diff --git a/web/src/use/utils/use-route-query-pagination.ts b/web/src/use/utils/use-route-query-pagination.ts new file mode 100644 index 0000000..5371095 --- /dev/null +++ b/web/src/use/utils/use-route-query-pagination.ts @@ -0,0 +1,41 @@ +import { useRouteQuery } from "@vueuse/router" + +import { integerTransformer } from "@/utils/use-route-query-transformers" + +const DEFAULT_PAGE = 1 +const DEFAULT_PER_PAGE = 10 + +/** + * Usage: use in conjunction with EnhancedPagination component. + * @param {Object} [options] - Options object. + * @param {number} [options.page=1] - Initial page number. Default is 1. + * @param {number} [options.perPage=10] - Initial items per page. Default is 10. + */ +export function useRouteQueryPagination({ + page = DEFAULT_PAGE, + perPage = DEFAULT_PER_PAGE, + routeQuerySuffix = "", +}: { + page?: number + perPage?: number + routeQuerySuffix?: string +} = {}) { + const queryPage = useRouteQuery(`page${routeQuerySuffix}`, page.toString(), { + transform: integerTransformer, + }) + + const queryPerPage = useRouteQuery( + `perPage${routeQuerySuffix}`, + perPage.toString(), + { + transform: integerTransformer, + } + ) + + return { + page: queryPage, + perPage: queryPerPage, + } +} + +export default useRouteQueryPagination diff --git a/web/src/use/utils/use-vuetify-color-classes-as-specific-color-classes.ts b/web/src/use/utils/use-vuetify-color-classes-as-specific-color-classes.ts new file mode 100644 index 0000000..152f2bc --- /dev/null +++ b/web/src/use/utils/use-vuetify-color-classes-as-specific-color-classes.ts @@ -0,0 +1,25 @@ +import { computed, Ref, toValue } from "vue" +import { isNil, isEmpty } from "lodash" + +/** + * Returns a computed ref that returns a specific color class based on the provided color classes. + * Given + * "red" could be used produce "text-red" or "bg-red" or "border-red" + */ +export function useVuetifyColorClassesAsSpecificColorClasses( + colorClasses: Ref, + prefix: string = "text" +) { + const textColorClass = computed(() => { + const colorClassesValues = toValue(colorClasses) + if (isNil(colorClassesValues)) return "" + if (isEmpty(colorClassesValues)) return "" + + const normalizedClass = colorClassesValues.split(" ").join("-") + return `${prefix}-${normalizedClass}` + }) + + return textColorClass +} + +export default useVuetifyColorClassesAsSpecificColorClasses diff --git a/web/src/use/utils/use-vuetify-color-name-to-hex-value.ts b/web/src/use/utils/use-vuetify-color-name-to-hex-value.ts new file mode 100644 index 0000000..b951904 --- /dev/null +++ b/web/src/use/utils/use-vuetify-color-name-to-hex-value.ts @@ -0,0 +1,12 @@ +import { useTheme } from "vuetify" + +/** + * Useful for setting the color of a tabler icon. + */ +export function useVuetifyColorNameToHexValue(colorName: string) { + const theme = useTheme() + + return theme.current.value.colors[colorName] +} + +export default useVuetifyColorNameToHexValue diff --git a/web/src/use/utils/use-vuetify-slot-names-pass-through.ts b/web/src/use/utils/use-vuetify-slot-names-pass-through.ts new file mode 100644 index 0000000..7c220ae --- /dev/null +++ b/web/src/use/utils/use-vuetify-slot-names-pass-through.ts @@ -0,0 +1,26 @@ +import { ComputedRef, computed, useSlots } from "vue" + +type ComponentWithSlots = { + $slots: { + [key: string]: unknown + } +} + +type SlotNamesExtractor = T extends ComponentWithSlots ? keyof T["$slots"] & string : never + +export function useVuetifySlotNamesPassThrough< + Component extends ComponentWithSlots = never, + SlotNames extends SlotNamesExtractor = SlotNamesExtractor, +>(slotsToPassThrough: SlotNames[]): ComputedRef { + const wrappedSlotNames = new Set(slotsToPassThrough) + + const slots = useSlots() + + return computed(() => { + return Object.keys(slots).filter((slotName): slotName is SlotNames => + wrappedSlotNames.has(slotName as SlotNames) + ) + }) +} + +export default useVuetifySlotNamesPassThrough diff --git a/web/src/use/utils/use-vuetify-sort-by-to-safe-route-query.ts b/web/src/use/utils/use-vuetify-sort-by-to-safe-route-query.ts new file mode 100644 index 0000000..03d6508 --- /dev/null +++ b/web/src/use/utils/use-vuetify-sort-by-to-safe-route-query.ts @@ -0,0 +1,53 @@ +import { isEmpty, isNil, isString } from "lodash" +import { Ref } from "vue" + +import { VDataTable } from "vuetify/components" + +import useRouteQueryEnhanced from "@/use/utils/use-route-query-enhanced" + +export type SortItem = VDataTable["sortBy"][0] + +/** + * Must not conflict with web/src/use/utils/use-vuetify-sort-by-to-sequelize-safe-order.ts SEPARATOR. + */ +const SEPARATOR = "_" + +export function useVuetifySortByToSafeRouteQuery( + name: string, + defaultValue?: SortItem[] | undefined +): Ref { + function parse(newSortBy: string[] | string | undefined): SortItem[] | undefined { + if (isNil(newSortBy) || isEmpty(newSortBy)) { + return + } + + if (isString(newSortBy)) { + newSortBy = [newSortBy] + } + + return newSortBy.map((entry) => { + const [key, order] = entry.split(SEPARATOR) + return { key, order } as SortItem + }) + } + + function stringify(sortByValue: SortItem[] | undefined): string[] | undefined { + if (isNil(sortByValue) || isEmpty(sortByValue)) { + return + } + + return sortByValue.map(({ key, order }) => `${key}${SEPARATOR}${order}`) + } + + const defaultValueString = stringify(defaultValue) + return useRouteQueryEnhanced( + name, + defaultValueString, + { + parse, + stringify, + } + ) +} + +export default useVuetifySortByToSafeRouteQuery diff --git a/web/src/use/utils/use-vuetify-sort-by-to-sequelize-safe-order.ts b/web/src/use/utils/use-vuetify-sort-by-to-sequelize-safe-order.ts new file mode 100644 index 0000000..558925b --- /dev/null +++ b/web/src/use/utils/use-vuetify-sort-by-to-sequelize-safe-order.ts @@ -0,0 +1,61 @@ +import { isEmpty, isNil } from "lodash" +import { computed, ComputedRef, Ref, toValue } from "vue" + +import { type ModelOrder } from "@/api/base-api" +import { type SortItem } from "@/use/utils/use-vuetify-sort-by-to-safe-route-query" + +/** + * Must not conflict with web/src/use/utils/use-vuetify-sort-by-to-safe-route-query.ts SEPARATOR + */ +const SEPARATOR = "." + +/** + * Converts content of the form [ + * { key: "attribute", order: "asc" }, + * { key: "attribute", order: "desc" }, + * { key: "nested.associated.attribute", order: "asc" }, + * { key: "nested.associated.attribute", order: "desc" }, + * ] + * + * to the form [ + * [ "attribute", "ASC" ], + * [ "attribute", "DESC" ], + * [ "nested", "associated", "attribute", "ASC" ], + * [ "nested", "associated", "attribute", "DESC" ], + * ] + */ +export function useVuetifySortByToSequelizeSafeOrder( + sortBy: Ref +): ComputedRef { + const order = computed(() => { + const sortByValue = toValue(sortBy) + if (isNil(sortByValue) || isEmpty(sortByValue)) { + return undefined + } + + return sortByValue.map(({ key: column, order }) => { + let direction: "ASC" | "DESC" = "ASC" + if (order === true) { + direction = "ASC" + } else if (order === false) { + direction = "DESC" + } else if (order === "desc") { + direction = "DESC" + } else if (order === "asc") { + direction = "ASC" + } + + const nestedAssociatedModelAttributes = column.split(SEPARATOR) as + | [string, string] + | [string, string, string] + | [string, string, string, string] + | [string, string, string, string, string] + + return [...nestedAssociatedModelAttributes, direction] + }) + }) + + return order +} + +export default useVuetifySortByToSequelizeSafeOrder diff --git a/web/src/utils/authorization-guards/authorization-guard.ts b/web/src/utils/authorization-guards/authorization-guard.ts new file mode 100644 index 0000000..ad34b8f --- /dev/null +++ b/web/src/utils/authorization-guards/authorization-guard.ts @@ -0,0 +1,37 @@ +import { type RouteLocationNormalized } from "vue-router" +import { isEmpty, isNil } from "lodash" + +import useCurrentUser, { type UserAsShow } from "@/use/use-current-user" + +export async function authorizationGuard(to: RouteLocationNormalized) { + const guardGroups = to.matched + .map((record) => record.meta.guards as ((user: UserAsShow) => boolean)[] | undefined) + .filter((g): g is ((user: UserAsShow) => boolean)[] => !isNil(g) && !isEmpty(g)) + + if (guardGroups.length === 0) return true + + const { currentUser, fetch } = useCurrentUser() + if (isNil(currentUser.value)) { + await fetch() + } + + if (isNil(currentUser.value)) { + throw new Error("No current user") + } + + // AND across matched records (every level must pass), OR within each + // record's guards array (any guard at that level satisfies it). + for (const guards of guardGroups) { + const passes = guards.some((guard) => { + if (typeof guard !== "function") { + throw new Error(`Guard ${guard} is not a function`) + } + return guard(currentUser.value as UserAsShow) + }) + if (!passes) return false + } + + return true +} + +export default authorizationGuard diff --git a/web/src/utils/authorization-guards/index.ts b/web/src/utils/authorization-guards/index.ts new file mode 100644 index 0000000..121f9c4 --- /dev/null +++ b/web/src/utils/authorization-guards/index.ts @@ -0,0 +1,2 @@ +export { authorizationGuard } from "./authorization-guard" +export { isSystemAdmin } from "./user-guards" diff --git a/web/src/utils/authorization-guards/user-guards.ts b/web/src/utils/authorization-guards/user-guards.ts new file mode 100644 index 0000000..b445fb3 --- /dev/null +++ b/web/src/utils/authorization-guards/user-guards.ts @@ -0,0 +1,6 @@ +import { UserRoles } from "@/api/users-api" +import { type UserAsShow } from "@/api/current-user-api" + +export function isSystemAdmin(user: UserAsShow): boolean { + return user.roles.includes(UserRoles.SYSTEM_ADMIN) ?? false +} diff --git a/web/src/utils/debounce-with-args-cache.ts b/web/src/utils/debounce-with-args-cache.ts new file mode 100644 index 0000000..ac25d6a --- /dev/null +++ b/web/src/utils/debounce-with-args-cache.ts @@ -0,0 +1,57 @@ +import { debounce } from "lodash" + +type DebounceableFunction = Parameters["0"] + +/** + * @example + * ``` + * const usersApi = { + * async get(userId, params = {}) { + * console.trace(`usersApi.get: Fetching user: ${userId} with params:`, params) + * const { data } = await http.get(`/api/users/${userId}`, { params }) + * return data + * }, + * } + * + * usersApi.get = debounceWithArgsCache(usersApi.get, 300, 10) + * + * usersApi.get(1, { role: "admin" }) + * usersApi.get(2, { role: "user" }) + * usersApi.get(1, { role: "admin" }) + * setTimeout(() => usersApi.get(1, { role: "admin" }), 400) + * ``` + */ +export function debounceWithArgsCache( + fn: DebounceableFunction, + { + wait = 300, + leading = true, + trailing = true, + cacheSize = 10, + }: { wait?: number; leading?: boolean; trailing?: boolean; cacheSize?: number } +) { + const invocationCache = new Map() + + return (...args: unknown[]) => { + const argsKey = JSON.stringify(args) + + if (invocationCache.has(argsKey)) { + const debouncedFn = invocationCache.get(argsKey) + const result = debouncedFn(...args) + return result + } + + if (invocationCache.size >= cacheSize) { + const oldestInvocation = invocationCache.keys().next().value + invocationCache.delete(oldestInvocation) + } + + const debouncedFn = debounce(fn, wait, { leading, trailing }) + invocationCache.set(argsKey, debouncedFn) + + const result = debouncedFn(...args) + return result + } +} + +export default debounceWithArgsCache diff --git a/web/src/utils/formatters/format-bytes.ts b/web/src/utils/formatters/format-bytes.ts new file mode 100644 index 0000000..3186eb0 --- /dev/null +++ b/web/src/utils/formatters/format-bytes.ts @@ -0,0 +1,13 @@ +export function formatBytes(bytes: number, decimals = 2) { + if (bytes === 0) return "0 Bytes" + + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] + + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i] +} + +export default formatBytes diff --git a/web/src/utils/formatters/format-date-long.ts b/web/src/utils/formatters/format-date-long.ts new file mode 100644 index 0000000..eb9f03d --- /dev/null +++ b/web/src/utils/formatters/format-date-long.ts @@ -0,0 +1,21 @@ +import { isNil, isEmpty } from "lodash" +import { DateTime } from "luxon" + +/** + * Note: \u00A0d is a non-breaking space, and permits date to wrap like this: + * November 07, 72024 + * @ 710:21 AM + */ +export function formatDateLong(input: string | Date | undefined): string { + if (isNil(input) || isEmpty(input)) { + return "" + } else if (typeof input == "string") { + return DateTime.fromISO(input).toLocal().toFormat("MMMM\u00A0dd,\u00A0yyyy @\u00A0t") + } else if (input instanceof Date) { + return DateTime.fromJSDate(input).toLocal().toFormat("MMMM\u00A0dd,\u00A0yyyy @\u00A0t") + } else { + return "" + } +} + +export default formatDateLong diff --git a/web/src/utils/formatters/format-date-words.ts b/web/src/utils/formatters/format-date-words.ts new file mode 100644 index 0000000..4e3f111 --- /dev/null +++ b/web/src/utils/formatters/format-date-words.ts @@ -0,0 +1,16 @@ +import { isNil, isEmpty } from "lodash" +import { DateTime } from "luxon" + +export function formatDateWords(input: string | Date | undefined): string { + if (isNil(input) || isEmpty(input)) { + return "" + } else if (typeof input == "string") { + return DateTime.fromISO(input).toLocal().toFormat("MMMM dd, yyyy") + } else if (input instanceof Date) { + return DateTime.fromJSDate(input).toLocal().toFormat("MMMM dd, yyyy") + } else { + return "" + } +} + +export default formatDateWords diff --git a/web/src/utils/formatters/format-date.ts b/web/src/utils/formatters/format-date.ts new file mode 100644 index 0000000..cf6bf2b --- /dev/null +++ b/web/src/utils/formatters/format-date.ts @@ -0,0 +1,18 @@ +import { isNil, isEmpty } from "lodash" +import { DateTime } from "luxon" + +export function formatDate(input: string | Date | undefined): string { + if (isNil(input) || isEmpty(input)) { + return "" + } else if (typeof input == "string") { + const parsed = DateTime.fromISO(input, { zone: "utc" }) + const isMidnight = parsed.hour === 0 && parsed.minute === 0 && parsed.second === 0 + return isMidnight ? parsed.toFormat("yyyy-MM-dd") : parsed.toLocal().toFormat("yyyy-MM-dd") + } else if (input instanceof Date) { + return DateTime.fromJSDate(input).toLocal().toFormat("yyyy-MM-dd") + } else { + return "" + } +} + +export default formatDate diff --git a/web/src/utils/formatters/format-number-in-string-to-fixed-decimal.ts b/web/src/utils/formatters/format-number-in-string-to-fixed-decimal.ts new file mode 100644 index 0000000..755ae23 --- /dev/null +++ b/web/src/utils/formatters/format-number-in-string-to-fixed-decimal.ts @@ -0,0 +1,24 @@ +import { isNil } from "lodash" + +/** + * Extracts the numeric part of a string and formats it to desired decimal places + * + * Ex. -15.030000000000001 Cycles => -15.03 Cycles + * + * NOTE: number of decimals defaults to 2 + */ +export function formatNumberInStringToFixedDecimal( + input: string | null | undefined, + decimals: number = 2 +): string { + if (isNil(input) || decimals < 0) { + console.warn( + `formatNumberInStringToFixedDecimal received invalid input: input=${input}, decimals=${decimals}` + ) + return "" + } + + return input.replace(/([-+]?\d[\d,]*\.?\d*)/, (match) => { + return parseFloat(match.replace(/,/g, "")).toFixed(decimals) + }) +} diff --git a/web/src/utils/formatters/format-relative.ts b/web/src/utils/formatters/format-relative.ts new file mode 100644 index 0000000..7fe65bf --- /dev/null +++ b/web/src/utils/formatters/format-relative.ts @@ -0,0 +1,16 @@ +import { isNil, isEmpty } from "lodash" +import { DateTime } from "luxon" + +export function formatRelative(input: string | Date | undefined): string { + if (isNil(input) || isEmpty(input)) { + return "" + } else if (typeof input == "string") { + return DateTime.fromISO(input).toLocal().toRelative() ?? "" + } else if (input instanceof Date) { + return DateTime.fromJSDate(input).toLocal().toRelative() ?? "" + } else { + return "" + } +} + +export default formatRelative diff --git a/web/src/utils/formatters/index.ts b/web/src/utils/formatters/index.ts new file mode 100644 index 0000000..60a99f3 --- /dev/null +++ b/web/src/utils/formatters/index.ts @@ -0,0 +1,6 @@ +export { formatBytes } from "./format-bytes" +export { formatDate } from "./format-date" +export { formatDateLong } from "./format-date-long" +export { formatRelative } from "./format-relative" +export { formatDateWords } from "./format-date-words" + diff --git a/web/src/utils/safe-json-parse.ts b/web/src/utils/safe-json-parse.ts new file mode 100644 index 0000000..136f19a --- /dev/null +++ b/web/src/utils/safe-json-parse.ts @@ -0,0 +1,14 @@ +export function safeJsonParse(values: string): unknown | null { + try { + return JSON.parse(values) + } catch (error) { + if (error instanceof SyntaxError) { + return null + } else { + console.warn(`Unexpected json parsing error: ${error}`, { error }) + return null + } + } +} + +export default safeJsonParse diff --git a/web/src/utils/sleep.ts b/web/src/utils/sleep.ts new file mode 100644 index 0000000..0c1353d --- /dev/null +++ b/web/src/utils/sleep.ts @@ -0,0 +1,3 @@ +export function sleep(milliseconds: number) { + return new Promise((resolve) => setTimeout(resolve, milliseconds)) +} diff --git a/web/src/utils/strip-trailing-slash.ts b/web/src/utils/strip-trailing-slash.ts new file mode 100644 index 0000000..94279ee --- /dev/null +++ b/web/src/utils/strip-trailing-slash.ts @@ -0,0 +1,3 @@ +export function stripTrailingSlash(url: string) { + return url.endsWith("/") ? url.slice(0, -1) : url +} diff --git a/web/src/utils/use-route-query-transformers/boolean-transformer.ts b/web/src/utils/use-route-query-transformers/boolean-transformer.ts new file mode 100644 index 0000000..7d927f3 --- /dev/null +++ b/web/src/utils/use-route-query-transformers/boolean-transformer.ts @@ -0,0 +1,22 @@ +import { isNil } from "lodash" + +export const booleanTransformer = { + get(value: boolean | string | null | undefined): boolean { + if (isNil(value)) return false + if (typeof value === "string") { + return value === "true" + } + + return value + }, + set(value: boolean | string | null | undefined): string { + if (isNil(value)) return "false" + if (typeof value === "boolean") { + return value.toString() + } + + return value + }, +} + +export default booleanTransformer diff --git a/web/src/utils/use-route-query-transformers/index.ts b/web/src/utils/use-route-query-transformers/index.ts new file mode 100644 index 0000000..23590a6 --- /dev/null +++ b/web/src/utils/use-route-query-transformers/index.ts @@ -0,0 +1,4 @@ +export { booleanTransformer } from "./boolean-transformer" +export { integerTransformer } from "./integer-transformer" +export { integerTransformerLegacy } from "./integer-transformer-legacy" +export { stringTransformer } from "./string-transformer" diff --git a/web/src/utils/use-route-query-transformers/integer-transformer-legacy.ts b/web/src/utils/use-route-query-transformers/integer-transformer-legacy.ts new file mode 100644 index 0000000..2faa83a --- /dev/null +++ b/web/src/utils/use-route-query-transformers/integer-transformer-legacy.ts @@ -0,0 +1,9 @@ +import { isNil } from "lodash" + +export function integerTransformerLegacy(value: string | null | undefined): number | null { + if (isNil(value)) return null + + return parseInt(value) +} + +export default integerTransformerLegacy diff --git a/web/src/utils/use-route-query-transformers/integer-transformer.ts b/web/src/utils/use-route-query-transformers/integer-transformer.ts new file mode 100644 index 0000000..d41c9a2 --- /dev/null +++ b/web/src/utils/use-route-query-transformers/integer-transformer.ts @@ -0,0 +1,27 @@ +import { isNil } from "lodash" + +function integerGet( + value: T +): number | (T & undefined) | (T & null) { + if (isNil(value)) return value + if (typeof value === "number") return value + + return parseInt(value, 10) +} + +function integerSet( + value: T +): string | (T & undefined) | (T & null) { + if (isNil(value)) return value + if (typeof value === "number") return value.toString() + if (typeof value === "string") return value + + return "" +} + +export const integerTransformer = { + get: integerGet, + set: integerSet, +} as const + +export default integerTransformer diff --git a/web/src/utils/use-route-query-transformers/string-transformer.ts b/web/src/utils/use-route-query-transformers/string-transformer.ts new file mode 100644 index 0000000..761923e --- /dev/null +++ b/web/src/utils/use-route-query-transformers/string-transformer.ts @@ -0,0 +1,16 @@ +import { isNil } from "lodash" + +export const stringTransformer = { + get(value: string | null | undefined) { + if (isNil(value)) return "" + + return value + }, + set(value: string | null | undefined) { + if (isNil(value)) return "" + + return value + }, +} + +export default stringTransformer diff --git a/web/src/utils/utility-types.ts b/web/src/utils/utility-types.ts new file mode 100644 index 0000000..5219ce9 --- /dev/null +++ b/web/src/utils/utility-types.ts @@ -0,0 +1,5 @@ +export type VueHtmlClass = string | Record | (string | Record)[] + +export type SetIntersection = { + [K in keyof T[number]]: K extends keyof T[0] ? Extract : never +} diff --git a/web/src/utils/validators/email.ts b/web/src/utils/validators/email.ts new file mode 100644 index 0000000..2eca2d3 --- /dev/null +++ b/web/src/utils/validators/email.ts @@ -0,0 +1,7 @@ +import isEmail from "validator/lib/isEmail" + +export function email(value: string) { + if (isEmail(value)) return true + + return "Invalid email" +} diff --git a/web/src/utils/validators/index.ts b/web/src/utils/validators/index.ts new file mode 100644 index 0000000..d628dcc --- /dev/null +++ b/web/src/utils/validators/index.ts @@ -0,0 +1,4 @@ +export { email } from "./email" +export { minimum } from "./minimum" +export { required } from "./required" +export { requiredFile } from "./required-file" diff --git a/web/src/utils/validators/minimum.ts b/web/src/utils/validators/minimum.ts new file mode 100644 index 0000000..0330c7a --- /dev/null +++ b/web/src/utils/validators/minimum.ts @@ -0,0 +1,13 @@ +import { isString } from "lodash" + +export function minimum(length: number) { + return (v: unknown): boolean | string => { + if (isString(v) && v.length < length) { + return `Must be at least ${length} characters` + } + + return true + } +} + +export default minimum diff --git a/web/src/utils/validators/required-file.ts b/web/src/utils/validators/required-file.ts new file mode 100644 index 0000000..1d7d583 --- /dev/null +++ b/web/src/utils/validators/required-file.ts @@ -0,0 +1,20 @@ +export function requiredFile(v: unknown): boolean | string { + // Handle File object + if (v instanceof File) { + return true + } + + // Handle null or undefined + if (v === null || v === undefined) { + return "File is required" + } + + // Handle empty array (when file input is cleared) + if (Array.isArray(v) && v.length === 0) { + return "File is required" + } + + return "File is required" +} + +export default requiredFile diff --git a/web/src/utils/validators/required.ts b/web/src/utils/validators/required.ts new file mode 100644 index 0000000..8391522 --- /dev/null +++ b/web/src/utils/validators/required.ts @@ -0,0 +1,15 @@ +import { isArray, isEmpty, isString, isNull, isObject, isUndefined } from "lodash" + +export function required(v: unknown): boolean | string { + if (isNull(v) || isUndefined(v)) { + return "This field is required" + } + + if ((isArray(v) || isString(v) || isObject(v)) && isEmpty(v)) { + return "This field is required" + } + + return true +} + +export default required diff --git a/web/src/vite-env.d.ts b/web/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/web/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/web/tests/setup.ts b/web/tests/setup.ts new file mode 100644 index 0000000..55b92b1 --- /dev/null +++ b/web/tests/setup.ts @@ -0,0 +1,7 @@ +import { mockHttpClient } from "@/tests/support/mock-http-client" +import { mockCurrentUserApi } from "@/tests/support/mock-current-user-api" + +beforeEach(() => { + mockHttpClient() + mockCurrentUserApi() +}) diff --git a/web/tests/support/index.ts b/web/tests/support/index.ts new file mode 100644 index 0000000..16dc4b1 --- /dev/null +++ b/web/tests/support/index.ts @@ -0,0 +1,2 @@ +export { mockCurrentUserApi } from "./mock-current-user-api" +export { mockHttpClient } from "./mock-http-client" diff --git a/web/tests/support/mock-current-user-api.ts b/web/tests/support/mock-current-user-api.ts new file mode 100644 index 0000000..a9da4f6 --- /dev/null +++ b/web/tests/support/mock-current-user-api.ts @@ -0,0 +1,54 @@ +import { merge } from "lodash" + +import { type Policy } from "@/api/base-api" +import currentUserApi, { type UserAsShow } from "@/api/current-user-api" + +export const DEFAULT_USER: UserAsShow = { + id: 1, + email: "default-mock-user@example.com", + firstName: null, + lastName: null, + displayName: null, + title: null, + isActive: true, + emailNotificationsEnabled: true, + createdAt: "1970-01-01T00:00:00.000Z", + updatedAt: "1970-01-01T00:00:00.000Z", +} +export const DEFAULT_POLICY: Policy = { + show: true, + create: false, + update: true, + destroy: false, +} + +/** + * Usage: + * At the top section of a test file import: + * import { mockCurrentUserApi } from "@/tests/support" + * + * Then where you want to set the current user: + * mockCurrentUserApi({ user, policy }) + * + * Note that all values for user and policy are optional as minium safe defaults are provided. + * Note that order of operations matters. This file must be imported before any file that imports currentUserApi. + * As such this file has been imported and mocked in the pre-test run "web/tests/setup.ts" file. + * + * @param currentUserApiGetResponseWithDefaults - The response to set for the current user + */ +export function mockCurrentUserApi( + currentUserApiGetResponseWithDefaults: { user?: UserAsShow; policy?: Policy } = {} +) { + vi.mock("@/api/current-user-api") + + const userWithDefaults = merge({}, DEFAULT_USER, currentUserApiGetResponseWithDefaults.user) + const policyWithDefaults = merge({}, DEFAULT_POLICY, currentUserApiGetResponseWithDefaults.policy) + + const mockedCurrentUserApi = vi.mocked(currentUserApi, true) + mockedCurrentUserApi.get.mockResolvedValue({ + user: userWithDefaults, + policy: policyWithDefaults, + }) +} + +export default mockCurrentUserApi diff --git a/web/tests/support/mock-http-client.ts b/web/tests/support/mock-http-client.ts new file mode 100644 index 0000000..330a79f --- /dev/null +++ b/web/tests/support/mock-http-client.ts @@ -0,0 +1,40 @@ +import httpClient from "@/api/http-client" + +vi.mock("@/api/http-client") + +/** + * Usage: + * At the top section of a test file import: + * import { mockHttpClient } from "@/tests/support" + * + * Then where you want to set the http client: + * mockHttpClient() + * + * Note that order of operations matters. This file must be imported before any file that imports httpClient. + * As such this file has been imported and mocked in the pre-test run "web/tests/setup.ts" file. + * + * @returns The mocked http client + */ +export function mockHttpClient() { + const httpClientMock = vi.mocked(httpClient, true) + + httpClientMock.get.mockResolvedValue({ + data: {}, + }) + httpClientMock.post.mockResolvedValue({ + data: {}, + }) + httpClientMock.put.mockResolvedValue({ + data: {}, + }) + httpClientMock.patch.mockResolvedValue({ + data: {}, + }) + httpClientMock.delete.mockResolvedValue({ + data: {}, + }) + + return httpClientMock +} + +export default mockHttpClient diff --git a/web/tests/tsconfig.json b/web/tests/tsconfig.json new file mode 100644 index 0000000..4e08243 --- /dev/null +++ b/web/tests/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "./../tsconfig.json", + "compilerOptions": { + "baseUrl": "./../", + "types": ["vitest/globals"], + "paths": { + "@/tests/*": ["./tests/*"], + "@/*": ["./src/*"] + } + }, + "include": ["../src/**/*", "../tests/**/*", ""] +} diff --git a/web/tests/utils/authorization-guards/authorization-guard.test.ts b/web/tests/utils/authorization-guards/authorization-guard.test.ts new file mode 100644 index 0000000..09ad5c9 --- /dev/null +++ b/web/tests/utils/authorization-guards/authorization-guard.test.ts @@ -0,0 +1,41 @@ +import { type RouteLocationNormalized } from "vue-router" + +import authorizationGuard from "@/utils/authorization-guards/authorization-guard" + +describe("web/src/utils/authorization-guards/authorization-guard.ts", () => { + describe("authorizationGuard", () => { + test("when there are no guards, returns true", async () => { + const to = { + meta: {}, + } as unknown as RouteLocationNormalized + + const result = await authorizationGuard(to) + + expect(result).toBe(true) + }) + + test("when all guards return true, returns true", async () => { + const to = { + meta: { + guards: [() => true, () => true, () => true], + }, + } as unknown as RouteLocationNormalized + + const result = await authorizationGuard(to) + + expect(result).toBe(true) + }) + + test("when any guard returns false, returns false", async () => { + const to = { + meta: { + guards: [() => true, () => false, () => true], + }, + } as unknown as RouteLocationNormalized + + const result = await authorizationGuard(to) + + expect(result).toBe(false) + }) + }) +}) diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 0000000..416ec29 --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "allowJs": true, + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "noEmit": true, + "paths": { + "@/*": ["./src/*", "./dist/*"] + } + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue", + "src/**/*.js", + ".eslintrc.cjs" + ], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/web/tsconfig.node.json b/web/tsconfig.node.json new file mode 100644 index 0000000..77ae975 --- /dev/null +++ b/web/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "allowJs": true, + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, + "outDir": "./dist" + }, + "include": ["vite.config.js"] +} diff --git a/web/vite.config.js b/web/vite.config.js new file mode 100644 index 0000000..1c13544 --- /dev/null +++ b/web/vite.config.js @@ -0,0 +1,45 @@ +/// +import { fileURLToPath, URL } from "node:url" + +// Plugins +import vue from "@vitejs/plugin-vue" +import vuetify from "vite-plugin-vuetify" + +// Utilities +import { defineConfig } from "vite" + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin + vuetify({ + autoImport: true, + }), + ], + build: { + outDir: "./dist", + }, + define: { "process.env": {} }, + resolve: { + alias: { + "@/tests": fileURLToPath(new URL("./tests", import.meta.url)), + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, + extensions: [".js", ".json", ".jsx", ".mjs", ".ts", ".tsx", ".vue"], + }, + server: { + port: 8080, + }, + test: { + environment: "jsdom", + globals: true, // https://vitest.dev/config/#globals + setupFiles: ["./tests/setup.ts"], + // Mocking + clearMocks: true, + mockReset: true, + restoreMocks: true, + unstubEnvs: true, + unstubGlobals: true, + }, +})