From cdbd3e1092474ae65fcd6064966a3bb8c297f907 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 11:43:36 +0800 Subject: [PATCH 01/80] chore(repo): initialize pnpm workspace and turbo pipeline --- .gitignore | 4 ++- package.json | 22 +++++++++++++ pnpm-lock.yaml | 78 +++++++++++++++++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 3 ++ turbo.json | 28 ++++++++++++++++ 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 turbo.json diff --git a/.gitignore b/.gitignore index 5fcdc0a..5074169 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ develop.md -.idea/ \ No newline at end of file +node_modules/ +.turbo/ +.idea/ diff --git a/package.json b/package.json new file mode 100644 index 0000000..58df56e --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "todolist", + "version": "0.1.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "turbo run test", + "dev": "turbo run dev --parallel", + "build": "turbo run build", + "lint": "turbo run lint", + "typecheck": "turbo run typecheck", + "format": "turbo run format" + }, + "keywords": [], + "author": "", + "license": "GPL-3.0-or-later", + "private": true, + "packageManager": "pnpm@9.15.2", + "devDependencies": { + "turbo": "^2.9.3" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..91f5927 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,78 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + turbo: + specifier: ^2.9.3 + version: 2.9.3 + +packages: + + '@turbo/darwin-64@2.9.3': + resolution: {integrity: sha512-P8foouaP+y/p+hhEGBoZpzMbpVvUMwPjDpcy6wN7EYfvvyISD1USuV27qWkczecihwuPJzQ1lDBuL8ERcavTyg==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.9.3': + resolution: {integrity: sha512-SIzEkvtNdzdI50FJDaIQ6kQGqgSSdFPcdn0wqmmONN6iGKjy6hsT+EH99GP65FsfV7DLZTh2NmtTIRl2kdoz5Q==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.9.3': + resolution: {integrity: sha512-pLRwFmcHHNBvsCySLS6OFabr/07kDT2pxEt/k6eBf/3asiVQZKJ7Rk88AafQx2aYA641qek4RsXvYO3JYpiBug==} + cpu: [x64] + os: [linux] + + '@turbo/linux-arm64@2.9.3': + resolution: {integrity: sha512-gy6ApUroC2Nzv+qjGtE/uPNkhHAFU4c8God+zd5Aiv9L9uBgHlxVJpHT3XWl5xwlJZ2KWuMrlHTaS5kmNB+q1Q==} + cpu: [arm64] + os: [linux] + + '@turbo/windows-64@2.9.3': + resolution: {integrity: sha512-d0YelTX6hAsB7kIEtGB3PzIzSfAg3yDoUlHwuwJc3adBXUsyUIs0YLG+1NNtuhcDOUGnWQeKUoJ2pGWvbpRj7w==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.9.3': + resolution: {integrity: sha512-/08CwpKJl3oRY8nOlh2YgilZVJDHsr60XTNxRhuDeuFXONpUZ5X+Nv65izbG/xBew9qxcJFbDX9/sAmAX+ITcQ==} + cpu: [arm64] + os: [win32] + + turbo@2.9.3: + resolution: {integrity: sha512-J/VUvsGRykPb9R8Kh8dHVBOqioDexLk9BhLCU/ZybRR+HN9UR3cURdazFvNgMDt9zPP8TF6K73Z+tplfmi0PqQ==} + hasBin: true + +snapshots: + + '@turbo/darwin-64@2.9.3': + optional: true + + '@turbo/darwin-arm64@2.9.3': + optional: true + + '@turbo/linux-64@2.9.3': + optional: true + + '@turbo/linux-arm64@2.9.3': + optional: true + + '@turbo/windows-64@2.9.3': + optional: true + + '@turbo/windows-arm64@2.9.3': + optional: true + + turbo@2.9.3: + optionalDependencies: + '@turbo/darwin-64': 2.9.3 + '@turbo/darwin-arm64': 2.9.3 + '@turbo/linux-64': 2.9.3 + '@turbo/linux-arm64': 2.9.3 + '@turbo/windows-64': 2.9.3 + '@turbo/windows-arm64': 2.9.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..3ff5faa --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - "apps/*" + - "packages/*" diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..9bdd423 --- /dev/null +++ b/turbo.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "dev": { + "cache": false, + "persistent": true + }, + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**", "build/**"] + }, + "lint": { + "dependsOn": ["^lint"], + "outputs": [] + }, + "test": { + "dependsOn": ["^test"], + "outputs": ["coverage/**"] + }, + "typecheck": { + "dependsOn": ["^typecheck"], + "outputs": [] + }, + "format": { + "outputs": [] + } + } +} From 80cc9827004e69e56e3dad4bfd6260f70f53057b Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 11:57:31 +0800 Subject: [PATCH 02/80] chore(config): add shared tsconfig and eslint packages --- packages/eslint-config/base.cjs | 16 ++++++++++++++++ packages/eslint-config/package.json | 11 +++++++++++ packages/tsconfig/base.json | 17 +++++++++++++++++ packages/tsconfig/nest-app.json | 13 +++++++++++++ packages/tsconfig/package.json | 12 ++++++++++++ packages/tsconfig/react-app.json | 9 +++++++++ pnpm-lock.yaml | 4 ++++ 7 files changed, 82 insertions(+) create mode 100644 packages/eslint-config/base.cjs create mode 100644 packages/eslint-config/package.json create mode 100644 packages/tsconfig/base.json create mode 100644 packages/tsconfig/nest-app.json create mode 100644 packages/tsconfig/package.json create mode 100644 packages/tsconfig/react-app.json diff --git a/packages/eslint-config/base.cjs b/packages/eslint-config/base.cjs new file mode 100644 index 0000000..6ff0a72 --- /dev/null +++ b/packages/eslint-config/base.cjs @@ -0,0 +1,16 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + env: { + browser: true, + es2022: true, + node: true + }, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module" + }, + rules: { + "no-console": "warn", + "no-debugger": "error" + } +}; diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json new file mode 100644 index 0000000..1d92afd --- /dev/null +++ b/packages/eslint-config/package.json @@ -0,0 +1,11 @@ +{ + "name": "@todolist/eslint-config", + "version": "0.1.0", + "description": "Shared ESLint config presets for TodoList", + "main": "base.cjs", + "license": "GPL-3.0-or-later", + "private": true, + "files": [ + "base.cjs" + ] +} diff --git a/packages/tsconfig/base.json b/packages/tsconfig/base.json new file mode 100644 index 0000000..d204bc0 --- /dev/null +++ b/packages/tsconfig/base.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noImplicitOverride": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/packages/tsconfig/nest-app.json b/packages/tsconfig/nest-app.json new file mode 100644 index 0000000..e6c45a9 --- /dev/null +++ b/packages/tsconfig/nest-app.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./base.json", + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "Node", + "lib": ["ES2022"], + "types": ["node"], + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "sourceMap": true + } +} diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json new file mode 100644 index 0000000..d34f6cb --- /dev/null +++ b/packages/tsconfig/package.json @@ -0,0 +1,12 @@ +{ + "name": "@todolist/tsconfig", + "version": "0.1.0", + "description": "Shared TypeScript config presets for TodoList", + "license": "GPL-3.0-or-later", + "private": true, + "files": [ + "base.json", + "react-app.json", + "nest-app.json" + ] +} diff --git a/packages/tsconfig/react-app.json b/packages/tsconfig/react-app.json new file mode 100644 index 0000000..a785dfb --- /dev/null +++ b/packages/tsconfig/react-app.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "jsx": "react-jsx", + "types": ["vite/client"] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91f5927..00cebb6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,10 @@ importers: specifier: ^2.9.3 version: 2.9.3 + packages/eslint-config: {} + + packages/tsconfig: {} + packages: '@turbo/darwin-64@2.9.3': From 31dbb70bfb59a7783652c734fae9a6be99bdc44c Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 12:00:42 +0800 Subject: [PATCH 03/80] chore(repo): scaffold apps and packages directories --- apps/admin/.gitkeep | 0 apps/api/.gitkeep | 0 apps/web/.gitkeep | 0 packages/sdk/.gitkeep | 0 packages/shared-types/.gitkeep | 0 packages/ui/.gitkeep | 0 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/admin/.gitkeep create mode 100644 apps/api/.gitkeep create mode 100644 apps/web/.gitkeep create mode 100644 packages/sdk/.gitkeep create mode 100644 packages/shared-types/.gitkeep create mode 100644 packages/ui/.gitkeep diff --git a/apps/admin/.gitkeep b/apps/admin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/api/.gitkeep b/apps/api/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/web/.gitkeep b/apps/web/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/sdk/.gitkeep b/packages/sdk/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/shared-types/.gitkeep b/packages/shared-types/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/ui/.gitkeep b/packages/ui/.gitkeep new file mode 100644 index 0000000..e69de29 From 91ce1afdfc13004468bd018f8b1fe344a8524e00 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 12:04:16 +0800 Subject: [PATCH 04/80] docs(architecture): add ADR template --- docs/architecture/ADR-template.md | 62 +++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/architecture/ADR-template.md diff --git a/docs/architecture/ADR-template.md b/docs/architecture/ADR-template.md new file mode 100644 index 0000000..150cc1b --- /dev/null +++ b/docs/architecture/ADR-template.md @@ -0,0 +1,62 @@ +# ADR-XXXX:<决策标题> + +- 状态:Proposed | Accepted | Deprecated | Superseded +- 日期:YYYY-MM-DD +- 决策人:<团队/人员> +- 关联需求: + +## 背景 + +描述当前问题、约束条件,以及为什么现在必须做出这项决策。 + +## 决策驱动因素 + +- <驱动因素 1> +- <驱动因素 2> +- <驱动因素 3> + +## 可选方案 + +1. <方案 A> +2. <方案 B> +3. <方案 C> + +## 最终决策 + +选择方案:**<方案 X>** + +说明选择该方案的理由,以及未选择其他方案的原因。 + +## 影响评估 + +### 正向影响 + +- <收益 1> +- <收益 2> + +### 负向影响 / 取舍 + +- <代价 1> +- <代价 2> + +## 实施计划 + +1. <步骤 1> +2. <步骤 2> +3. <步骤 3> + +## 回滚方案 + +说明当风险发生时,如何撤销或回退这项决策。 + +## 验证清单 + +- [ ] 单元测试 +- [ ] 集成测试 +- [ ] 性能检查 +- [ ] 安全检查 + +## 参考资料 + +- <参考资料 1> +- <参考资料 2> From 0b353ad6a0f3ce4825d7e3131aae9435950e25e0 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 12:23:11 +0800 Subject: [PATCH 05/80] chore(lint): configure eslint prettier and lint-staged --- .gitignore | 1 + .lintstagedrc.cjs | 4 + .prettierignore | 7 + .prettierrc.json | 6 + eslint.config.mjs | 29 ++ package.json | 10 +- pnpm-lock.yaml | 799 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 854 insertions(+), 2 deletions(-) create mode 100644 .lintstagedrc.cjs create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 eslint.config.mjs diff --git a/.gitignore b/.gitignore index 5074169..ee6d817 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ develop.md node_modules/ .turbo/ .idea/ +.eslintcache diff --git a/.lintstagedrc.cjs b/.lintstagedrc.cjs new file mode 100644 index 0000000..b7ddb53 --- /dev/null +++ b/.lintstagedrc.cjs @@ -0,0 +1,4 @@ +module.exports = { + "*.{js,mjs,cjs,ts,tsx}": ["eslint --fix", "prettier --write"], + "*.{json,md,yml,yaml}": ["prettier --write"] +}; diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..1613f18 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +node_modules +.turbo +.idea +dist +build +coverage +*.png diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..8a0f27e --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "none", + "printWidth": 100 +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..665e53b --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,29 @@ +import js from "@eslint/js"; +import globals from "globals"; + +export default [ + { + ignores: ["**/node_modules/**", "**/.turbo/**", "**/dist/**", "**/build/**"] + }, + js.configs.recommended, + { + files: ["**/*.{js,mjs}"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + globals: { + ...globals.node + } + } + }, + { + files: ["**/*.cjs"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "commonjs", + globals: { + ...globals.node + } + } + } +]; diff --git a/package.json b/package.json index 58df56e..a1ae063 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,10 @@ "test": "turbo run test", "dev": "turbo run dev --parallel", "build": "turbo run build", - "lint": "turbo run lint", + "lint": "turbo run lint && eslint .", "typecheck": "turbo run typecheck", - "format": "turbo run format" + "format": "prettier --write .", + "lint:staged": "lint-staged" }, "keywords": [], "author": "", @@ -17,6 +18,11 @@ "private": true, "packageManager": "pnpm@9.15.2", "devDependencies": { + "@eslint/js": "^10.0.1", + "eslint": "^10.2.0", + "globals": "^17.4.0", + "lint-staged": "^16.4.0", + "prettier": "^3.8.1", "turbo": "^2.9.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00cebb6..3310851 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,21 @@ importers: .: devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.2.0) + eslint: + specifier: ^10.2.0 + version: 10.2.0 + globals: + specifier: ^17.4.0 + version: 17.4.0 + lint-staged: + specifier: ^16.4.0 + version: 16.4.0 + prettier: + specifier: ^3.8.1 + version: 3.8.1 turbo: specifier: ^2.9.3 version: 2.9.3 @@ -18,6 +33,61 @@ importers: packages: + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.4': + resolution: {integrity: sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.5.4': + resolution: {integrity: sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.0': + resolution: {integrity: sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.4': + resolution: {integrity: sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.0': + resolution: {integrity: sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@turbo/darwin-64@2.9.3': resolution: {integrity: sha512-P8foouaP+y/p+hhEGBoZpzMbpVvUMwPjDpcy6wN7EYfvvyISD1USuV27qWkczecihwuPJzQ1lDBuL8ERcavTyg==} cpu: [x64] @@ -48,12 +118,409 @@ packages: cpu: [arm64] os: [win32] + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.2.0: + resolution: {integrity: sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@17.4.0: + resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} + engines: {node: '>=18'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lint-staged@16.4.0: + resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==} + engines: {node: '>=20.17'} + hasBin: true + + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.0: + resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} + engines: {node: '>=20'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + tinyexec@1.0.4: + resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} + engines: {node: '>=18'} + turbo@2.9.3: resolution: {integrity: sha512-J/VUvsGRykPb9R8Kh8dHVBOqioDexLk9BhLCU/ZybRR+HN9UR3cURdazFvNgMDt9zPP8TF6K73Z+tplfmi0PqQ==} hasBin: true + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + snapshots: + '@eslint-community/eslint-utils@4.9.1(eslint@10.2.0)': + dependencies: + eslint: 10.2.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.4': + dependencies: + '@eslint/object-schema': 3.0.4 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.5.4': + dependencies: + '@eslint/core': 1.2.0 + + '@eslint/core@1.2.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.2.0)': + optionalDependencies: + eslint: 10.2.0 + + '@eslint/object-schema@3.0.4': {} + + '@eslint/plugin-kit@0.7.0': + dependencies: + '@eslint/core': 1.2.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@turbo/darwin-64@2.9.3': optional: true @@ -72,6 +539,314 @@ snapshots: '@turbo/windows-arm64@2.9.3': optional: true + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@6.2.2: {} + + ansi-styles@6.2.3: {} + + balanced-match@4.0.4: {} + + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@5.2.0: + dependencies: + slice-ansi: 8.0.0 + string-width: 8.2.0 + + colorette@2.0.20: {} + + commander@14.0.3: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + emoji-regex@10.6.0: {} + + environment@1.1.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.2.0: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.4 + '@eslint/config-helpers': 0.5.4 + '@eslint/core': 1.2.0 + '@eslint/plugin-kit': 0.7.0 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + eventemitter3@5.0.4: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + get-east-asian-width@1.5.0: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@17.4.0: {} + + ignore@5.3.2: {} + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.5.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lint-staged@16.4.0: + dependencies: + commander: 14.0.3 + listr2: 9.0.5 + picomatch: 4.0.4 + string-argv: 0.3.2 + tinyexec: 1.0.4 + yaml: 2.8.3 + + listr2@9.0.5: + dependencies: + cli-truncate: 5.2.0 + colorette: 2.0.20 + eventemitter3: 5.0.4 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + mimic-function@5.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picomatch@4.0.4: {} + + prelude-ls@1.2.1: {} + + prettier@3.8.1: {} + + punycode@2.3.1: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + rfdc@1.4.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + slice-ansi@8.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + string-argv@0.3.2: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + string-width@8.2.0: + dependencies: + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + tinyexec@1.0.4: {} + turbo@2.9.3: optionalDependencies: '@turbo/darwin-64': 2.9.3 @@ -80,3 +855,27 @@ snapshots: '@turbo/linux-arm64': 2.9.3 '@turbo/windows-64': 2.9.3 '@turbo/windows-arm64': 2.9.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + yaml@2.8.3: {} + + yocto-queue@0.1.0: {} From 0ccb086598d803ac7715160ebfe5950538325bfc Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 12:25:35 +0800 Subject: [PATCH 06/80] chore(husky): add pre-commit and pre-push hooks --- .gitignore | 1 + .husky/pre-commit | 1 + .husky/pre-push | 2 + package.json | 4 +- pnpm-lock.yaml | 854 ++++++++++++++++++++++++++++++++-------------- 5 files changed, 601 insertions(+), 261 deletions(-) create mode 100755 .husky/pre-commit create mode 100755 .husky/pre-push diff --git a/.gitignore b/.gitignore index ee6d817..ce34796 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules/ .turbo/ .idea/ .eslintcache +/.husky/_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..f9241ee --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +pnpm lint:staged diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..4226722 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,2 @@ +pnpm typecheck +pnpm test diff --git a/package.json b/package.json index a1ae063..057bdd4 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "lint": "turbo run lint && eslint .", "typecheck": "turbo run typecheck", "format": "prettier --write .", - "lint:staged": "lint-staged" + "lint:staged": "lint-staged", + "prepare": "husky" }, "keywords": [], "author": "", @@ -21,6 +22,7 @@ "@eslint/js": "^10.0.1", "eslint": "^10.2.0", "globals": "^17.4.0", + "husky": "^9.1.7", "lint-staged": "^16.4.0", "prettier": "^3.8.1", "turbo": "^2.9.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3310851..3c014fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,14 +1,13 @@ -lockfileVersion: '9.0' +lockfileVersion: "9.0" settings: autoInstallPeers: true excludeLinksFromLockfile: false importers: - .: devDependencies: - '@eslint/js': + "@eslint/js": specifier: ^10.0.1 version: 10.0.1(eslint@10.2.0) eslint: @@ -17,6 +16,9 @@ importers: globals: specifier: ^17.4.0 version: 17.4.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 lint-staged: specifier: ^16.4.0 version: 16.4.0 @@ -32,518 +34,848 @@ importers: packages/tsconfig: {} packages: - - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + "@eslint-community/eslint-utils@4.9.1": + resolution: + { + integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + "@eslint-community/regexpp@4.12.2": + resolution: + { + integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } - '@eslint/config-array@0.23.4': - resolution: {integrity: sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + "@eslint/config-array@0.23.4": + resolution: + { + integrity: sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow== + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } - '@eslint/config-helpers@0.5.4': - resolution: {integrity: sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + "@eslint/config-helpers@0.5.4": + resolution: + { + integrity: sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg== + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } - '@eslint/core@1.2.0': - resolution: {integrity: sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + "@eslint/core@1.2.0": + resolution: + { + integrity: sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A== + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } - '@eslint/js@10.0.1': - resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + "@eslint/js@10.0.1": + resolution: + { + integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA== + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } peerDependencies: eslint: ^10.0.0 peerDependenciesMeta: eslint: optional: true - '@eslint/object-schema@3.0.4': - resolution: {integrity: sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + "@eslint/object-schema@3.0.4": + resolution: + { + integrity: sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw== + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } - '@eslint/plugin-kit@0.7.0': - resolution: {integrity: sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + "@eslint/plugin-kit@0.7.0": + resolution: + { + integrity: sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg== + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} + "@humanfs/core@0.19.1": + resolution: + { + integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + } + engines: { node: ">=18.18.0" } - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} - engines: {node: '>=18.18.0'} + "@humanfs/node@0.16.7": + resolution: + { + integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== + } + engines: { node: ">=18.18.0" } - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} + "@humanwhocodes/module-importer@1.0.1": + resolution: + { + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + } + engines: { node: ">=12.22" } - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} + "@humanwhocodes/retry@0.4.3": + resolution: + { + integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + } + engines: { node: ">=18.18" } - '@turbo/darwin-64@2.9.3': - resolution: {integrity: sha512-P8foouaP+y/p+hhEGBoZpzMbpVvUMwPjDpcy6wN7EYfvvyISD1USuV27qWkczecihwuPJzQ1lDBuL8ERcavTyg==} + "@turbo/darwin-64@2.9.3": + resolution: + { + integrity: sha512-P8foouaP+y/p+hhEGBoZpzMbpVvUMwPjDpcy6wN7EYfvvyISD1USuV27qWkczecihwuPJzQ1lDBuL8ERcavTyg== + } cpu: [x64] os: [darwin] - '@turbo/darwin-arm64@2.9.3': - resolution: {integrity: sha512-SIzEkvtNdzdI50FJDaIQ6kQGqgSSdFPcdn0wqmmONN6iGKjy6hsT+EH99GP65FsfV7DLZTh2NmtTIRl2kdoz5Q==} + "@turbo/darwin-arm64@2.9.3": + resolution: + { + integrity: sha512-SIzEkvtNdzdI50FJDaIQ6kQGqgSSdFPcdn0wqmmONN6iGKjy6hsT+EH99GP65FsfV7DLZTh2NmtTIRl2kdoz5Q== + } cpu: [arm64] os: [darwin] - '@turbo/linux-64@2.9.3': - resolution: {integrity: sha512-pLRwFmcHHNBvsCySLS6OFabr/07kDT2pxEt/k6eBf/3asiVQZKJ7Rk88AafQx2aYA641qek4RsXvYO3JYpiBug==} + "@turbo/linux-64@2.9.3": + resolution: + { + integrity: sha512-pLRwFmcHHNBvsCySLS6OFabr/07kDT2pxEt/k6eBf/3asiVQZKJ7Rk88AafQx2aYA641qek4RsXvYO3JYpiBug== + } cpu: [x64] os: [linux] - '@turbo/linux-arm64@2.9.3': - resolution: {integrity: sha512-gy6ApUroC2Nzv+qjGtE/uPNkhHAFU4c8God+zd5Aiv9L9uBgHlxVJpHT3XWl5xwlJZ2KWuMrlHTaS5kmNB+q1Q==} + "@turbo/linux-arm64@2.9.3": + resolution: + { + integrity: sha512-gy6ApUroC2Nzv+qjGtE/uPNkhHAFU4c8God+zd5Aiv9L9uBgHlxVJpHT3XWl5xwlJZ2KWuMrlHTaS5kmNB+q1Q== + } cpu: [arm64] os: [linux] - '@turbo/windows-64@2.9.3': - resolution: {integrity: sha512-d0YelTX6hAsB7kIEtGB3PzIzSfAg3yDoUlHwuwJc3adBXUsyUIs0YLG+1NNtuhcDOUGnWQeKUoJ2pGWvbpRj7w==} + "@turbo/windows-64@2.9.3": + resolution: + { + integrity: sha512-d0YelTX6hAsB7kIEtGB3PzIzSfAg3yDoUlHwuwJc3adBXUsyUIs0YLG+1NNtuhcDOUGnWQeKUoJ2pGWvbpRj7w== + } cpu: [x64] os: [win32] - '@turbo/windows-arm64@2.9.3': - resolution: {integrity: sha512-/08CwpKJl3oRY8nOlh2YgilZVJDHsr60XTNxRhuDeuFXONpUZ5X+Nv65izbG/xBew9qxcJFbDX9/sAmAX+ITcQ==} + "@turbo/windows-arm64@2.9.3": + resolution: + { + integrity: sha512-/08CwpKJl3oRY8nOlh2YgilZVJDHsr60XTNxRhuDeuFXONpUZ5X+Nv65izbG/xBew9qxcJFbDX9/sAmAX+ITcQ== + } cpu: [arm64] os: [win32] - '@types/esrecurse@4.3.1': - resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + "@types/esrecurse@4.3.1": + resolution: + { + integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw== + } - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + "@types/estree@1.0.8": + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + } - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + "@types/json-schema@7.0.15": + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + } acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + } peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} + resolution: + { + integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== + } + engines: { node: ">=0.4.0" } hasBin: true ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + resolution: + { + integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== + } ansi-escapes@7.3.0: - resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg== + } + engines: { node: ">=18" } ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== + } + engines: { node: ">=12" } ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== + } + engines: { node: ">=12" } balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} + resolution: + { + integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + } + engines: { node: 18 || 20 || >=22 } brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} - engines: {node: 18 || 20 || >=22} + resolution: + { + integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== + } + engines: { node: 18 || 20 || >=22 } cli-cursor@5.0.0: - resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== + } + engines: { node: ">=18" } cli-truncate@5.2.0: - resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} - engines: {node: '>=20'} + resolution: + { + integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw== + } + engines: { node: ">=20" } colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + resolution: + { + integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + } commander@14.0.3: - resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} - engines: {node: '>=20'} + resolution: + { + integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw== + } + engines: { node: ">=20" } cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + } + engines: { node: ">= 8" } debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} + resolution: + { + integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + } + engines: { node: ">=6.0" } peerDependencies: - supports-color: '*' + supports-color: "*" peerDependenciesMeta: supports-color: optional: true deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + } emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + resolution: + { + integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A== + } environment@1.1.0: - resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== + } + engines: { node: ">=18" } escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + } + engines: { node: ">=10" } eslint-scope@9.1.2: - resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + resolution: + { + integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ== + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } eslint-visitor-keys@5.0.1: - resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + resolution: + { + integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } eslint@10.2.0: - resolution: {integrity: sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + resolution: + { + integrity: sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA== + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } hasBin: true peerDependencies: - jiti: '*' + jiti: "*" peerDependenciesMeta: jiti: optional: true espree@11.2.0: - resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + resolution: + { + integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw== + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} + resolution: + { + integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== + } + engines: { node: ">=0.10" } esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + } + engines: { node: ">=4.0" } estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + } + engines: { node: ">=4.0" } esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + } + engines: { node: ">=0.10.0" } eventemitter3@5.0.4: - resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + resolution: + { + integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== + } fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + } fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + } fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + } file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} + resolution: + { + integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + } + engines: { node: ">=16.0.0" } find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + } + engines: { node: ">=10" } flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} + resolution: + { + integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + } + engines: { node: ">=16" } flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + resolution: + { + integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== + } get-east-asian-width@1.5.0: - resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA== + } + engines: { node: ">=18" } glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + } + engines: { node: ">=10.13.0" } globals@17.4.0: - resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw== + } + engines: { node: ">=18" } + + husky@9.1.7: + resolution: + { + integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== + } + engines: { node: ">=18" } + hasBin: true ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + } + engines: { node: ">= 4" } imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + } + engines: { node: ">=0.8.19" } is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + } + engines: { node: ">=0.10.0" } is-fullwidth-code-point@5.1.0: - resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ== + } + engines: { node: ">=18" } is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + } + engines: { node: ">=0.10.0" } isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + } json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + } json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + } json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + } keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + } levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + } + engines: { node: ">= 0.8.0" } lint-staged@16.4.0: - resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==} - engines: {node: '>=20.17'} + resolution: + { + integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw== + } + engines: { node: ">=20.17" } hasBin: true listr2@9.0.5: - resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} - engines: {node: '>=20.0.0'} + resolution: + { + integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g== + } + engines: { node: ">=20.0.0" } locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + } + engines: { node: ">=10" } log-update@6.1.0: - resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w== + } + engines: { node: ">=18" } mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== + } + engines: { node: ">=18" } minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} + resolution: + { + integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== + } + engines: { node: 18 || 20 || >=22 } ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + } natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + } onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== + } + engines: { node: ">=18" } optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + } + engines: { node: ">= 0.8.0" } p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + } + engines: { node: ">=10" } p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + } + engines: { node: ">=10" } path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + } + engines: { node: ">=8" } path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + } + engines: { node: ">=8" } picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== + } + engines: { node: ">=12" } prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + } + engines: { node: ">= 0.8.0" } prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== + } + engines: { node: ">=14" } hasBin: true punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + } + engines: { node: ">=6" } restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== + } + engines: { node: ">=18" } rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + resolution: + { + integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + } shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + } + engines: { node: ">=8" } shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + } + engines: { node: ">=8" } signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + } + engines: { node: ">=14" } slice-ansi@7.1.2: - resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w== + } + engines: { node: ">=18" } slice-ansi@8.0.0: - resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} - engines: {node: '>=20'} + resolution: + { + integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg== + } + engines: { node: ">=20" } string-argv@0.3.2: - resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} - engines: {node: '>=0.6.19'} + resolution: + { + integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== + } + engines: { node: ">=0.6.19" } string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + } + engines: { node: ">=18" } string-width@8.2.0: - resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} - engines: {node: '>=20'} + resolution: + { + integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw== + } + engines: { node: ">=20" } strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w== + } + engines: { node: ">=12" } tinyexec@1.0.4: - resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw== + } + engines: { node: ">=18" } turbo@2.9.3: - resolution: {integrity: sha512-J/VUvsGRykPb9R8Kh8dHVBOqioDexLk9BhLCU/ZybRR+HN9UR3cURdazFvNgMDt9zPP8TF6K73Z+tplfmi0PqQ==} + resolution: + { + integrity: sha512-J/VUvsGRykPb9R8Kh8dHVBOqioDexLk9BhLCU/ZybRR+HN9UR3cURdazFvNgMDt9zPP8TF6K73Z+tplfmi0PqQ== + } hasBin: true type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + } + engines: { node: ">= 0.8.0" } uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + } which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + } + engines: { node: ">= 8" } hasBin: true word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + } + engines: { node: ">=0.10.0" } wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww== + } + engines: { node: ">=18" } yaml@2.8.3: - resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} - engines: {node: '>= 14.6'} + resolution: + { + integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg== + } + engines: { node: ">= 14.6" } hasBin: true yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + } + engines: { node: ">=10" } snapshots: - - '@eslint-community/eslint-utils@4.9.1(eslint@10.2.0)': + "@eslint-community/eslint-utils@4.9.1(eslint@10.2.0)": dependencies: eslint: 10.2.0 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.2': {} + "@eslint-community/regexpp@4.12.2": {} - '@eslint/config-array@0.23.4': + "@eslint/config-array@0.23.4": dependencies: - '@eslint/object-schema': 3.0.4 + "@eslint/object-schema": 3.0.4 debug: 4.4.3 minimatch: 10.2.5 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.5.4': + "@eslint/config-helpers@0.5.4": dependencies: - '@eslint/core': 1.2.0 + "@eslint/core": 1.2.0 - '@eslint/core@1.2.0': + "@eslint/core@1.2.0": dependencies: - '@types/json-schema': 7.0.15 + "@types/json-schema": 7.0.15 - '@eslint/js@10.0.1(eslint@10.2.0)': + "@eslint/js@10.0.1(eslint@10.2.0)": optionalDependencies: eslint: 10.2.0 - '@eslint/object-schema@3.0.4': {} + "@eslint/object-schema@3.0.4": {} - '@eslint/plugin-kit@0.7.0': + "@eslint/plugin-kit@0.7.0": dependencies: - '@eslint/core': 1.2.0 + "@eslint/core": 1.2.0 levn: 0.4.1 - '@humanfs/core@0.19.1': {} + "@humanfs/core@0.19.1": {} - '@humanfs/node@0.16.7': + "@humanfs/node@0.16.7": dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.4.3 + "@humanfs/core": 0.19.1 + "@humanwhocodes/retry": 0.4.3 - '@humanwhocodes/module-importer@1.0.1': {} + "@humanwhocodes/module-importer@1.0.1": {} - '@humanwhocodes/retry@0.4.3': {} + "@humanwhocodes/retry@0.4.3": {} - '@turbo/darwin-64@2.9.3': + "@turbo/darwin-64@2.9.3": optional: true - '@turbo/darwin-arm64@2.9.3': + "@turbo/darwin-arm64@2.9.3": optional: true - '@turbo/linux-64@2.9.3': + "@turbo/linux-64@2.9.3": optional: true - '@turbo/linux-arm64@2.9.3': + "@turbo/linux-arm64@2.9.3": optional: true - '@turbo/windows-64@2.9.3': + "@turbo/windows-64@2.9.3": optional: true - '@turbo/windows-arm64@2.9.3': + "@turbo/windows-arm64@2.9.3": optional: true - '@types/esrecurse@4.3.1': {} + "@types/esrecurse@4.3.1": {} - '@types/estree@1.0.8': {} + "@types/estree@1.0.8": {} - '@types/json-schema@7.0.15': {} + "@types/json-schema@7.0.15": {} acorn-jsx@5.3.2(acorn@8.16.0): dependencies: @@ -605,8 +937,8 @@ snapshots: eslint-scope@9.1.2: dependencies: - '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.8 + "@types/esrecurse": 4.3.1 + "@types/estree": 1.0.8 esrecurse: 4.3.0 estraverse: 5.3.0 @@ -616,16 +948,16 @@ snapshots: eslint@10.2.0: dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.4 - '@eslint/config-helpers': 0.5.4 - '@eslint/core': 1.2.0 - '@eslint/plugin-kit': 0.7.0 - '@humanfs/node': 0.16.7 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 + "@eslint-community/eslint-utils": 4.9.1(eslint@10.2.0) + "@eslint-community/regexpp": 4.12.2 + "@eslint/config-array": 0.23.4 + "@eslint/config-helpers": 0.5.4 + "@eslint/core": 1.2.0 + "@eslint/plugin-kit": 0.7.0 + "@humanfs/node": 0.16.7 + "@humanwhocodes/module-importer": 1.0.1 + "@humanwhocodes/retry": 0.4.3 + "@types/estree": 1.0.8 ajv: 6.14.0 cross-spawn: 7.0.6 debug: 4.4.3 @@ -699,6 +1031,8 @@ snapshots: globals@17.4.0: {} + husky@9.1.7: {} + ignore@5.3.2: {} imurmurhash@0.1.4: {} @@ -849,12 +1183,12 @@ snapshots: turbo@2.9.3: optionalDependencies: - '@turbo/darwin-64': 2.9.3 - '@turbo/darwin-arm64': 2.9.3 - '@turbo/linux-64': 2.9.3 - '@turbo/linux-arm64': 2.9.3 - '@turbo/windows-64': 2.9.3 - '@turbo/windows-arm64': 2.9.3 + "@turbo/darwin-64": 2.9.3 + "@turbo/darwin-arm64": 2.9.3 + "@turbo/linux-64": 2.9.3 + "@turbo/linux-arm64": 2.9.3 + "@turbo/windows-64": 2.9.3 + "@turbo/windows-arm64": 2.9.3 type-check@0.4.0: dependencies: From 80ff8ab7651667d9267d986be1800d6a9430fd5b Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 12:26:32 +0800 Subject: [PATCH 07/80] docs(contributing): define commit and branch conventions --- CONTRIBUTING.md | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..728d44f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,69 @@ +# 贡献指南(Contributing) + +本文档定义 TodoList 仓库的协作规范,所有贡献者提交代码前请先阅读。 + +## 1. 分支模型 + +- 长期分支: + - `main`:生产稳定分支 + - `develop`:开发集成分支 +- 功能分支: + - 命名:`feature/-` + - 示例:`feature/p1-code-quality-hooks` +- 其他分支: + - `release/` + - `hotfix/-` + +## 2. 提交流程 + +1. 从目标基线分支切出功能分支。 +2. 每完成一个小功能,提交一个最小 commit。 +3. 完成后推送分支并创建 PR。 +4. 通过 Code Review 后再合并到目标分支。 + +## 3. Commit 规范 + +- 使用 Conventional Commits: + - `feat(scope): ...` + - `fix(scope): ...` + - `chore(scope): ...` + - `docs(scope): ...` + - `test(scope): ...` + - `ci(scope): ...` +- 要求: + - commit 粒度最小化,不要把多个不相关改动塞进一个提交。 + - commit 必须可回滚、可解释。 + - 默认使用 GPG 签名提交:`git commit -S`。 + +## 4. PR 规范 + +- PR 标题简明描述变更目标。 +- PR 描述至少包含: + - 变更概述 + - 具体改动 + - 测试结果 + - 风险评估 + - 回滚方案 +- 一个 PR 只解决一类问题,避免“超大 PR”。 + +## 5. 代码质量检查 + +提交前建议至少执行: + +```bash +pnpm install +pnpm run lint +pnpm run typecheck +pnpm run test +``` + +说明: + +- `pre-commit` 会自动执行 `lint-staged`。 +- `pre-push` 会自动执行 `typecheck + test`。 + +## 6. 变更边界要求 + +- 不要提交无关文件(例如本地 IDE 缓存、临时导出文件)。 +- 不要随意修改与当前任务无关的历史代码。 +- 如发现仓库出现非本人预期改动,先暂停并和维护者确认。 From 7fbfac6b87aaf3ce5244fdfedeea58042e16fc81 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 12:46:01 +0800 Subject: [PATCH 08/80] ci(actions): add pull request quality workflow --- .github/workflows/pr-quality.yml | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/pr-quality.yml diff --git a/.github/workflows/pr-quality.yml b/.github/workflows/pr-quality.yml new file mode 100644 index 0000000..2dbdb43 --- /dev/null +++ b/.github/workflows/pr-quality.yml @@ -0,0 +1,46 @@ +name: PR Quality + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [main, develop] + +concurrency: + group: pr-quality-${{ github.ref }} + cancel-in-progress: true + +jobs: + quality: + name: Lint, Typecheck, Test, Build + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.2 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm run lint + + - name: Typecheck + run: pnpm run typecheck + + - name: Test + run: pnpm run test + + - name: Build + run: pnpm run build From 44cc2a0ee6e536e3c380dbf151f85415a449c736 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 12:46:55 +0800 Subject: [PATCH 09/80] ci(actions): add api docker image workflow --- .github/workflows/api-docker-image.yml | 66 ++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/api-docker-image.yml diff --git a/.github/workflows/api-docker-image.yml b/.github/workflows/api-docker-image.yml new file mode 100644 index 0000000..685084a --- /dev/null +++ b/.github/workflows/api-docker-image.yml @@ -0,0 +1,66 @@ +name: API Docker Image + +on: + pull_request: + branches: [main, develop] + paths: + - "apps/api/**" + - ".github/workflows/api-docker-image.yml" + push: + branches: [main] + paths: + - "apps/api/**" + - ".github/workflows/api-docker-image.yml" + workflow_dispatch: + +concurrency: + group: api-docker-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-publish: + name: Build API Docker Image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check Dockerfile + id: dockerfile + run: | + if [ -f apps/api/Dockerfile ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Setup Docker Buildx + if: steps.dockerfile.outputs.exists == 'true' + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + if: steps.dockerfile.outputs.exists == 'true' && github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build (PR/manual) or Build and Push (main) + if: steps.dockerfile.outputs.exists == 'true' + uses: docker/build-push-action@v6 + with: + context: ./apps/api + file: ./apps/api/Dockerfile + push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + tags: | + ghcr.io/${{ github.repository }}/api:${{ github.sha }} + ghcr.io/${{ github.repository }}/api:latest + + - name: Skip notice + if: steps.dockerfile.outputs.exists != 'true' + run: echo "apps/api/Dockerfile not found, skip docker build." From d5677107683c479c9e5dc02543d0033f1df9f911 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 12:47:58 +0800 Subject: [PATCH 10/80] ci(actions): add web and admin deploy workflow templates --- .github/workflows/deploy-admin.yml | 59 ++++++++++++++++++++++++++++++ .github/workflows/deploy-web.yml | 59 ++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 .github/workflows/deploy-admin.yml create mode 100644 .github/workflows/deploy-web.yml diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml new file mode 100644 index 0000000..4c1f691 --- /dev/null +++ b/.github/workflows/deploy-admin.yml @@ -0,0 +1,59 @@ +name: Deploy Admin + +on: + push: + branches: [main] + paths: + - "apps/admin/**" + - "packages/shared-types/**" + - "packages/ui/**" + - ".github/workflows/deploy-admin.yml" + workflow_dispatch: + +concurrency: + group: deploy-admin-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build Admin + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.2 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build workspace + run: pnpm run build + + deploy: + name: Deploy Admin (Template) + runs-on: ubuntu-latest + needs: build + + steps: + - name: Trigger deployment webhook + env: + ADMIN_DEPLOY_WEBHOOK_URL: ${{ secrets.ADMIN_DEPLOY_WEBHOOK_URL }} + run: | + if [ -z "$ADMIN_DEPLOY_WEBHOOK_URL" ]; then + echo "ADMIN_DEPLOY_WEBHOOK_URL is not configured. Skipping deploy." + exit 0 + fi + + curl -X POST "$ADMIN_DEPLOY_WEBHOOK_URL" + echo "Admin deployment webhook triggered." diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml new file mode 100644 index 0000000..a55a8be --- /dev/null +++ b/.github/workflows/deploy-web.yml @@ -0,0 +1,59 @@ +name: Deploy Web + +on: + push: + branches: [main] + paths: + - "apps/web/**" + - "packages/shared-types/**" + - "packages/ui/**" + - ".github/workflows/deploy-web.yml" + workflow_dispatch: + +concurrency: + group: deploy-web-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build Web + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.2 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build workspace + run: pnpm run build + + deploy: + name: Deploy Web (Template) + runs-on: ubuntu-latest + needs: build + + steps: + - name: Trigger deployment webhook + env: + WEB_DEPLOY_WEBHOOK_URL: ${{ secrets.WEB_DEPLOY_WEBHOOK_URL }} + run: | + if [ -z "$WEB_DEPLOY_WEBHOOK_URL" ]; then + echo "WEB_DEPLOY_WEBHOOK_URL is not configured. Skipping deploy." + exit 0 + fi + + curl -X POST "$WEB_DEPLOY_WEBHOOK_URL" + echo "Web deployment webhook triggered." From fba9443a30534528abdfc4d3b7f2c3e6a34b4313 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 12:58:14 +0800 Subject: [PATCH 11/80] feat(api-db): initialize prisma with postgresql datasource --- apps/api/.env.example | 1 + apps/api/.gitignore | 5 + apps/api/.gitkeep | 0 apps/api/package.json | 19 + apps/api/prisma.config.ts | 14 + apps/api/prisma/schema.prisma | 13 + pnpm-lock.yaml | 1130 ++++++++++++++++++++++++++++++++- 7 files changed, 1174 insertions(+), 8 deletions(-) create mode 100644 apps/api/.env.example create mode 100644 apps/api/.gitignore delete mode 100644 apps/api/.gitkeep create mode 100644 apps/api/package.json create mode 100644 apps/api/prisma.config.ts create mode 100644 apps/api/prisma/schema.prisma diff --git a/apps/api/.env.example b/apps/api/.env.example new file mode 100644 index 0000000..203f3a0 --- /dev/null +++ b/apps/api/.env.example @@ -0,0 +1 @@ +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/todolist?schema=public" diff --git a/apps/api/.gitignore b/apps/api/.gitignore new file mode 100644 index 0000000..9f62ec0 --- /dev/null +++ b/apps/api/.gitignore @@ -0,0 +1,5 @@ +node_modules +# Keep environment variables out of version control +.env + +/generated/prisma diff --git a/apps/api/.gitkeep b/apps/api/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 0000000..fd3daf9 --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,19 @@ +{ + "name": "@todolist/api", + "version": "0.1.0", + "description": "TodoList API service", + "scripts": { + "prisma:generate": "prisma generate", + "prisma:format": "prisma format", + "prisma:validate": "prisma validate" + }, + "license": "GPL-3.0-or-later", + "devDependencies": { + "dotenv": "^16.6.1", + "prisma": "^7.6.0" + }, + "private": true, + "dependencies": { + "@prisma/client": "^7.6.0" + } +} diff --git a/apps/api/prisma.config.ts b/apps/api/prisma.config.ts new file mode 100644 index 0000000..8f3a0eb --- /dev/null +++ b/apps/api/prisma.config.ts @@ -0,0 +1,14 @@ +// This file was generated by Prisma, and assumes you have installed the following: +// npm install --save-dev prisma dotenv +import "dotenv/config"; +import { defineConfig } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations" + }, + datasource: { + url: process.env["DATABASE_URL"] + } +}); diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma new file mode 100644 index 0000000..8bddec1 --- /dev/null +++ b/apps/api/prisma/schema.prisma @@ -0,0 +1,13 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Get a free hosted Postgres database in seconds: `npx create-db` + +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} + +datasource db { + provider = "postgresql" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c014fa..48e6d7f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,10 +9,10 @@ importers: devDependencies: "@eslint/js": specifier: ^10.0.1 - version: 10.0.1(eslint@10.2.0) + version: 10.0.1(eslint@10.2.0(jiti@2.6.1)) eslint: specifier: ^10.2.0 - version: 10.2.0 + version: 10.2.0(jiti@2.6.1) globals: specifier: ^17.4.0 version: 17.4.0 @@ -29,11 +29,59 @@ importers: specifier: ^2.9.3 version: 2.9.3 + apps/api: + dependencies: + "@prisma/client": + specifier: ^7.6.0 + version: 7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + devDependencies: + dotenv: + specifier: ^16.6.1 + version: 16.6.1 + prisma: + specifier: ^7.6.0 + version: 7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + packages/eslint-config: {} packages/tsconfig: {} packages: + "@clack/core@0.5.0": + resolution: + { + integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow== + } + + "@clack/prompts@0.11.0": + resolution: + { + integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw== + } + + "@electric-sql/pglite-socket@0.1.1": + resolution: + { + integrity: sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw== + } + hasBin: true + peerDependencies: + "@electric-sql/pglite": 0.4.1 + + "@electric-sql/pglite-tools@0.3.1": + resolution: + { + integrity: sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA== + } + peerDependencies: + "@electric-sql/pglite": 0.4.1 + + "@electric-sql/pglite@0.4.1": + resolution: + { + integrity: sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q== + } + "@eslint-community/eslint-utils@4.9.1": resolution: { @@ -97,6 +145,15 @@ packages: } engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + "@hono/node-server@1.19.11": + resolution: + { + integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g== + } + engines: { node: ">=18.14.1" } + peerDependencies: + hono: ^4 + "@humanfs/core@0.19.1": resolution: { @@ -125,6 +182,215 @@ packages: } engines: { node: ">=18.18" } + "@kurkle/color@0.3.4": + resolution: + { + integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w== + } + + "@prisma/client-runtime-utils@7.6.0": + resolution: + { + integrity: sha512-fD7jlqubsZvVODKvsp9lOpXVecx2aWGxC2l35Ioz2t+teUJ5CfR0SAMsi7UkU1VvaZmmm+DS6BdujF622nY7tQ== + } + + "@prisma/client@7.6.0": + resolution: + { + integrity: sha512-7Pe/1ayh3GgWPEg4mmT4ax77LJ1wC+XlnIFvQ94bLP2DsUnOpnruQQR3Jw7r+Frthk94QqDNxo3FjSg8h9PXeQ== + } + engines: { node: ^20.19 || ^22.12 || >=24.0 } + peerDependencies: + prisma: "*" + typescript: ">=5.4.0" + peerDependenciesMeta: + prisma: + optional: true + typescript: + optional: true + + "@prisma/config@7.6.0": + resolution: + { + integrity: sha512-MuAz1MK4PeG5/03YzfzX3CnFVHQ6qePGwUpQRzPzX5tT0ffJ3Tzi9zJZbBc+VzEGFCM8ghW/gTVDR85Syjt+Yw== + } + + "@prisma/debug@7.2.0": + resolution: + { + integrity: sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw== + } + + "@prisma/debug@7.6.0": + resolution: + { + integrity: sha512-LpHr3qos4lQZ6sxwjStf59YBht7m9/QF7NSQsMH6qGENWZu2w3UkQUGn1h5iRkDjnWRj3VHykOu9qFhps4ADvA== + } + + "@prisma/dev@0.24.3": + resolution: + { + integrity: sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg== + } + + "@prisma/engines-version@7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711": + resolution: + { + integrity: sha512-r51DLcJ8bDRSrBEJF3J4cinoWyGA7rfP2mG6lD90VqIbGNOkbfcLcXalSVjq5Y6brQS3vcjrq4GbyUb1Cb7vkw== + } + + "@prisma/engines@7.6.0": + resolution: + { + integrity: sha512-Sn5edRzhHqgRV2M+A0eIbY442B4mReWWf3pKs/LKreYgW7oa/up8JtK/s4iv/EQA097cyboZ08mmkpbLp+tZ3w== + } + + "@prisma/fetch-engine@7.6.0": + resolution: + { + integrity: sha512-N575Ni95c3FkduWY/eKTHqNYgNbceZ1tQaSknVtJjpKmiiBXmniESn/GTxsDvICC4ZeiNrXxioGInzQrCdx16w== + } + + "@prisma/get-platform@7.2.0": + resolution: + { + integrity: sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA== + } + + "@prisma/get-platform@7.6.0": + resolution: + { + integrity: sha512-ohZDwXvtmnbzOcutR2D13lDWpZP1wQjmPyztmt0AwXLzQI7q95EE7NYCvS+M6N6SivT+BM0NOqLmTH3wms4L3A== + } + + "@prisma/query-plan-executor@7.2.0": + resolution: + { + integrity: sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ== + } + + "@prisma/streams-local@0.1.2": + resolution: + { + integrity: sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg== + } + engines: { bun: ">=1.3.6", node: ">=22.0.0" } + + "@prisma/studio-core@0.27.3": + resolution: + { + integrity: sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw== + } + engines: { node: ^20.19 || ^22.12 || >=24.0, pnpm: "8" } + peerDependencies: + "@types/react": ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + "@radix-ui/primitive@1.1.3": + resolution: + { + integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg== + } + + "@radix-ui/react-compose-refs@1.1.2": + resolution: + { + integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg== + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-primitive@2.1.3": + resolution: + { + integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ== + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-slot@1.2.3": + resolution: + { + integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A== + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-toggle@1.1.10": + resolution: + { + integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ== + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-use-controllable-state@1.2.2": + resolution: + { + integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg== + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-effect-event@0.0.2": + resolution: + { + integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA== + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-layout-effect@1.1.1": + resolution: + { + integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ== + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@standard-schema/spec@1.1.0": + resolution: + { + integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== + } + "@turbo/darwin-64@2.9.3": resolution: { @@ -191,6 +457,12 @@ packages: integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== } + "@types/react@19.2.14": + resolution: + { + integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== + } + acorn-jsx@5.3.2: resolution: { @@ -213,6 +485,12 @@ packages: integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== } + ajv@8.18.0: + resolution: + { + integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== + } + ansi-escapes@7.3.0: resolution: { @@ -234,6 +512,13 @@ packages: } engines: { node: ">=12" } + aws-ssl-profiles@1.1.2: + resolution: + { + integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g== + } + engines: { node: ">= 6.0.0" } + balanced-match@4.0.4: resolution: { @@ -241,6 +526,13 @@ packages: } engines: { node: 18 || 20 || >=22 } + better-result@2.7.0: + resolution: + { + integrity: sha512-7zrmXjAK8u8Z6SOe4R65XObOR5X+Y2I/VVku3t5cPOGQ8/WsBcfFmfnIPiEl5EBMDOzPHRwbiPbMtQBKYdw7RA== + } + hasBin: true + brace-expansion@5.0.5: resolution: { @@ -248,6 +540,43 @@ packages: } engines: { node: 18 || 20 || >=22 } + c12@3.1.0: + resolution: + { + integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw== + } + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + + chart.js@4.5.1: + resolution: + { + integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw== + } + engines: { pnpm: ">=8" } + + chokidar@4.0.3: + resolution: + { + integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + } + engines: { node: ">= 14.16.0" } + + citty@0.1.6: + resolution: + { + integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ== + } + + citty@0.2.2: + resolution: + { + integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w== + } + cli-cursor@5.0.0: resolution: { @@ -275,6 +604,19 @@ packages: } engines: { node: ">=20" } + confbox@0.2.4: + resolution: + { + integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ== + } + + consola@3.4.2: + resolution: + { + integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== + } + engines: { node: ^14.18.0 || >=16.10.0 } + cross-spawn@7.0.6: resolution: { @@ -282,6 +624,12 @@ packages: } engines: { node: ">= 8" } + csstype@3.2.3: + resolution: + { + integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== + } + debug@4.4.3: resolution: { @@ -300,12 +648,65 @@ packages: integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== } + deepmerge-ts@7.1.5: + resolution: + { + integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw== + } + engines: { node: ">=16.0.0" } + + defu@6.1.6: + resolution: + { + integrity: sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug== + } + + denque@2.1.0: + resolution: + { + integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + } + engines: { node: ">=0.10" } + + destr@2.0.5: + resolution: + { + integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA== + } + + dotenv@16.6.1: + resolution: + { + integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== + } + engines: { node: ">=12" } + + effect@3.20.0: + resolution: + { + integrity: sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw== + } + emoji-regex@10.6.0: resolution: { integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A== } + empathic@2.0.0: + resolution: + { + integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA== + } + engines: { node: ">=14" } + + env-paths@3.0.0: + resolution: + { + integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + environment@1.1.0: resolution: { @@ -395,6 +796,19 @@ packages: integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== } + exsolve@1.0.8: + resolution: + { + integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA== + } + + fast-check@3.23.2: + resolution: + { + integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A== + } + engines: { node: ">=8.0.0" } + fast-deep-equal@3.1.3: resolution: { @@ -413,6 +827,12 @@ packages: integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== } + fast-uri@3.1.0: + resolution: + { + integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + } + file-entry-cache@8.0.0: resolution: { @@ -440,6 +860,19 @@ packages: integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== } + foreground-child@3.3.1: + resolution: + { + integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + } + engines: { node: ">=14" } + + generate-function@2.3.1: + resolution: + { + integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + } + get-east-asian-width@1.5.0: resolution: { @@ -447,6 +880,19 @@ packages: } engines: { node: ">=18" } + get-port-please@3.2.0: + resolution: + { + integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A== + } + + giget@2.0.0: + resolution: + { + integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA== + } + hasBin: true + glob-parent@6.0.2: resolution: { @@ -461,6 +907,37 @@ packages: } engines: { node: ">=18" } + graceful-fs@4.2.11: + resolution: + { + integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + } + + grammex@3.1.12: + resolution: + { + integrity: sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ== + } + + graphmatch@1.1.1: + resolution: + { + integrity: sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg== + } + + hono@4.12.10: + resolution: + { + integrity: sha512-mx/p18PLy5og9ufies2GOSUqep98Td9q4i/EF6X7yJgAiIopxqdfIO3jbqsi3jRgTgw88jMDEzVKi+V2EF+27w== + } + engines: { node: ">=16.9.0" } + + http-status-codes@2.3.0: + resolution: + { + integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA== + } + husky@9.1.7: resolution: { @@ -469,6 +946,13 @@ packages: engines: { node: ">=18" } hasBin: true + iconv-lite@0.7.2: + resolution: + { + integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== + } + engines: { node: ">=0.10.0" } + ignore@5.3.2: resolution: { @@ -504,12 +988,25 @@ packages: } engines: { node: ">=0.10.0" } + is-property@1.0.2: + resolution: + { + integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== + } + isexe@2.0.0: resolution: { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } + jiti@2.6.1: + resolution: + { + integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ== + } + hasBin: true + json-buffer@3.0.1: resolution: { @@ -522,6 +1019,12 @@ packages: integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== } + json-schema-traverse@1.0.0: + resolution: + { + integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + } + json-stable-stringify-without-jsonify@1.0.1: resolution: { @@ -570,6 +1073,19 @@ packages: } engines: { node: ">=18" } + long@5.3.2: + resolution: + { + integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + } + + lru.min@1.1.4: + resolution: + { + integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA== + } + engines: { bun: ">=1.0.0", deno: ">=1.30.0", node: ">=8.0.0" } + mimic-function@5.0.1: resolution: { @@ -590,12 +1106,46 @@ packages: integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== } + mysql2@3.15.3: + resolution: + { + integrity: sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg== + } + engines: { node: ">= 8.0" } + + named-placeholders@1.1.6: + resolution: + { + integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w== + } + engines: { node: ">=8.0.0" } + natural-compare@1.4.0: resolution: { integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== } + node-fetch-native@1.6.7: + resolution: + { + integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q== + } + + nypm@0.6.5: + resolution: + { + integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ== + } + engines: { node: ">=18" } + hasBin: true + + ohash@2.0.11: + resolution: + { + integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ== + } + onetime@7.0.0: resolution: { @@ -638,6 +1188,24 @@ packages: } engines: { node: ">=8" } + pathe@2.0.3: + resolution: + { + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + } + + perfect-debounce@1.0.0: + resolution: + { + integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA== + } + + picocolors@1.1.1: + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + } + picomatch@4.0.4: resolution: { @@ -645,6 +1213,19 @@ packages: } engines: { node: ">=12" } + pkg-types@2.3.0: + resolution: + { + integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig== + } + + postgres@3.4.7: + resolution: + { + integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw== + } + engines: { node: ">=12" } + prelude-ls@1.2.1: resolution: { @@ -660,6 +1241,28 @@ packages: engines: { node: ">=14" } hasBin: true + prisma@7.6.0: + resolution: + { + integrity: sha512-OKJIPT81K3+F+AayIkY/Y3mkF2NWoFh7lZApaaqPYy7EHILKdO0VsmGkP+hDKYTySHsFSyLWXm/JgcR1B8fY1Q== + } + engines: { node: ^20.19 || ^22.12 || >=24.0 } + hasBin: true + peerDependencies: + better-sqlite3: ">=9.0.0" + typescript: ">=5.4.0" + peerDependenciesMeta: + better-sqlite3: + optional: true + typescript: + optional: true + + proper-lockfile@4.1.2: + resolution: + { + integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== + } + punycode@2.3.1: resolution: { @@ -667,6 +1270,53 @@ packages: } engines: { node: ">=6" } + pure-rand@6.1.0: + resolution: + { + integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + } + + rc9@2.1.2: + resolution: + { + integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg== + } + + react-dom@19.2.4: + resolution: + { + integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ== + } + peerDependencies: + react: ^19.2.4 + + react@19.2.4: + resolution: + { + integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ== + } + engines: { node: ">=0.10.0" } + + readdirp@4.1.2: + resolution: + { + integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + } + engines: { node: ">= 14.18.0" } + + remeda@2.33.4: + resolution: + { + integrity: sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ== + } + + require-from-string@2.0.2: + resolution: + { + integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + } + engines: { node: ">=0.10.0" } + restore-cursor@5.1.0: resolution: { @@ -674,12 +1324,37 @@ packages: } engines: { node: ">=18" } + retry@0.12.0: + resolution: + { + integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + } + engines: { node: ">= 4" } + rfdc@1.4.1: resolution: { integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== } + safer-buffer@2.1.2: + resolution: + { + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + } + + scheduler@0.27.0: + resolution: + { + integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== + } + + seq-queue@0.0.5: + resolution: + { + integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q== + } + shebang-command@2.0.0: resolution: { @@ -694,6 +1369,12 @@ packages: } engines: { node: ">=8" } + signal-exit@3.0.7: + resolution: + { + integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + } + signal-exit@4.1.0: resolution: { @@ -701,6 +1382,12 @@ packages: } engines: { node: ">=14" } + sisteransi@1.0.5: + resolution: + { + integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + } + slice-ansi@7.1.2: resolution: { @@ -715,6 +1402,19 @@ packages: } engines: { node: ">=20" } + sqlstring@2.3.3: + resolution: + { + integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg== + } + engines: { node: ">= 0.6" } + + std-env@3.10.0: + resolution: + { + integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== + } + string-argv@0.3.2: resolution: { @@ -770,6 +1470,17 @@ packages: integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== } + valibot@1.2.0: + resolution: + { + integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg== + } + peerDependencies: + typescript: ">=5" + peerDependenciesMeta: + typescript: + optional: true + which@2.0.2: resolution: { @@ -807,10 +1518,37 @@ packages: } engines: { node: ">=10" } + zeptomatch@2.1.0: + resolution: + { + integrity: sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA== + } + snapshots: - "@eslint-community/eslint-utils@4.9.1(eslint@10.2.0)": + "@clack/core@0.5.0": dependencies: - eslint: 10.2.0 + picocolors: 1.1.1 + sisteransi: 1.0.5 + + "@clack/prompts@0.11.0": + dependencies: + "@clack/core": 0.5.0 + picocolors: 1.1.1 + sisteransi: 1.0.5 + + "@electric-sql/pglite-socket@0.1.1(@electric-sql/pglite@0.4.1)": + dependencies: + "@electric-sql/pglite": 0.4.1 + + "@electric-sql/pglite-tools@0.3.1(@electric-sql/pglite@0.4.1)": + dependencies: + "@electric-sql/pglite": 0.4.1 + + "@electric-sql/pglite@0.4.1": {} + + "@eslint-community/eslint-utils@4.9.1(eslint@10.2.0(jiti@2.6.1))": + dependencies: + eslint: 10.2.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 "@eslint-community/regexpp@4.12.2": {} @@ -831,9 +1569,9 @@ snapshots: dependencies: "@types/json-schema": 7.0.15 - "@eslint/js@10.0.1(eslint@10.2.0)": + "@eslint/js@10.0.1(eslint@10.2.0(jiti@2.6.1))": optionalDependencies: - eslint: 10.2.0 + eslint: 10.2.0(jiti@2.6.1) "@eslint/object-schema@3.0.4": {} @@ -842,6 +1580,10 @@ snapshots: "@eslint/core": 1.2.0 levn: 0.4.1 + "@hono/node-server@1.19.11(hono@4.12.10)": + dependencies: + hono: 4.12.10 + "@humanfs/core@0.19.1": {} "@humanfs/node@0.16.7": @@ -853,6 +1595,149 @@ snapshots: "@humanwhocodes/retry@0.4.3": {} + "@kurkle/color@0.3.4": {} + + "@prisma/client-runtime-utils@7.6.0": {} + + "@prisma/client@7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))": + dependencies: + "@prisma/client-runtime-utils": 7.6.0 + optionalDependencies: + prisma: 7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + "@prisma/config@7.6.0": + dependencies: + c12: 3.1.0 + deepmerge-ts: 7.1.5 + effect: 3.20.0 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast + + "@prisma/debug@7.2.0": {} + + "@prisma/debug@7.6.0": {} + + "@prisma/dev@0.24.3": + dependencies: + "@electric-sql/pglite": 0.4.1 + "@electric-sql/pglite-socket": 0.1.1(@electric-sql/pglite@0.4.1) + "@electric-sql/pglite-tools": 0.3.1(@electric-sql/pglite@0.4.1) + "@hono/node-server": 1.19.11(hono@4.12.10) + "@prisma/get-platform": 7.2.0 + "@prisma/query-plan-executor": 7.2.0 + "@prisma/streams-local": 0.1.2 + foreground-child: 3.3.1 + get-port-please: 3.2.0 + hono: 4.12.10 + http-status-codes: 2.3.0 + pathe: 2.0.3 + proper-lockfile: 4.1.2 + remeda: 2.33.4 + std-env: 3.10.0 + valibot: 1.2.0 + zeptomatch: 2.1.0 + transitivePeerDependencies: + - typescript + + "@prisma/engines-version@7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711": {} + + "@prisma/engines@7.6.0": + dependencies: + "@prisma/debug": 7.6.0 + "@prisma/engines-version": 7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711 + "@prisma/fetch-engine": 7.6.0 + "@prisma/get-platform": 7.6.0 + + "@prisma/fetch-engine@7.6.0": + dependencies: + "@prisma/debug": 7.6.0 + "@prisma/engines-version": 7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711 + "@prisma/get-platform": 7.6.0 + + "@prisma/get-platform@7.2.0": + dependencies: + "@prisma/debug": 7.2.0 + + "@prisma/get-platform@7.6.0": + dependencies: + "@prisma/debug": 7.6.0 + + "@prisma/query-plan-executor@7.2.0": {} + + "@prisma/streams-local@0.1.2": + dependencies: + ajv: 8.18.0 + better-result: 2.7.0 + env-paths: 3.0.0 + proper-lockfile: 4.1.2 + + "@prisma/studio-core@0.27.3(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)": + dependencies: + "@radix-ui/react-toggle": 1.1.10(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + "@types/react": 19.2.14 + chart.js: 4.5.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + transitivePeerDependencies: + - "@types/react-dom" + + "@radix-ui/primitive@1.1.3": {} + + "@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.4)": + dependencies: + react: 19.2.4 + optionalDependencies: + "@types/react": 19.2.14 + + "@radix-ui/react-primitive@2.1.3(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)": + dependencies: + "@radix-ui/react-slot": 1.2.3(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + "@types/react": 19.2.14 + + "@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.4)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + "@types/react": 19.2.14 + + "@radix-ui/react-toggle@1.1.10(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)": + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-primitive": 2.1.3(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + "@types/react": 19.2.14 + + "@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.4)": + dependencies: + "@radix-ui/react-use-effect-event": 0.0.2(@types/react@19.2.14)(react@19.2.4) + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + "@types/react": 19.2.14 + + "@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.4)": + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + "@types/react": 19.2.14 + + "@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.4)": + dependencies: + react: 19.2.4 + optionalDependencies: + "@types/react": 19.2.14 + + "@standard-schema/spec@1.1.0": {} + "@turbo/darwin-64@2.9.3": optional: true @@ -877,6 +1762,10 @@ snapshots: "@types/json-schema@7.0.15": {} + "@types/react@19.2.14": + dependencies: + csstype: 3.2.3 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -890,6 +1779,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansi-escapes@7.3.0: dependencies: environment: 1.1.0 @@ -898,12 +1794,47 @@ snapshots: ansi-styles@6.2.3: {} + aws-ssl-profiles@1.1.2: {} + balanced-match@4.0.4: {} + better-result@2.7.0: + dependencies: + "@clack/prompts": 0.11.0 + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 + c12@3.1.0: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.4 + defu: 6.1.6 + dotenv: 16.6.1 + exsolve: 1.0.8 + giget: 2.0.0 + jiti: 2.6.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + + chart.js@4.5.1: + dependencies: + "@kurkle/color": 0.3.4 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + citty@0.1.6: + dependencies: + consola: 3.4.2 + + citty@0.2.2: {} + cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -917,20 +1848,45 @@ snapshots: commander@14.0.3: {} + confbox@0.2.4: {} + + consola@3.4.2: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + csstype@3.2.3: {} + debug@4.4.3: dependencies: ms: 2.1.3 deep-is@0.1.4: {} + deepmerge-ts@7.1.5: {} + + defu@6.1.6: {} + + denque@2.1.0: {} + + destr@2.0.5: {} + + dotenv@16.6.1: {} + + effect@3.20.0: + dependencies: + "@standard-schema/spec": 1.1.0 + fast-check: 3.23.2 + emoji-regex@10.6.0: {} + empathic@2.0.0: {} + + env-paths@3.0.0: {} + environment@1.1.0: {} escape-string-regexp@4.0.0: {} @@ -946,9 +1902,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.2.0: + eslint@10.2.0(jiti@2.6.1): dependencies: - "@eslint-community/eslint-utils": 4.9.1(eslint@10.2.0) + "@eslint-community/eslint-utils": 4.9.1(eslint@10.2.0(jiti@2.6.1)) "@eslint-community/regexpp": 4.12.2 "@eslint/config-array": 0.23.4 "@eslint/config-helpers": 0.5.4 @@ -978,6 +1934,8 @@ snapshots: minimatch: 10.2.5 natural-compare: 1.4.0 optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 transitivePeerDependencies: - supports-color @@ -1001,12 +1959,20 @@ snapshots: eventemitter3@5.0.4: {} + exsolve@1.0.8: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} + fast-uri@3.1.0: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -1023,16 +1989,50 @@ snapshots: flatted@3.4.2: {} + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + generate-function@2.3.1: + dependencies: + is-property: 1.0.2 + get-east-asian-width@1.5.0: {} + get-port-please@3.2.0: {} + + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.6 + node-fetch-native: 1.6.7 + nypm: 0.6.5 + pathe: 2.0.3 + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 globals@17.4.0: {} + graceful-fs@4.2.11: {} + + grammex@3.1.12: {} + + graphmatch@1.1.1: {} + + hono@4.12.10: {} + + http-status-codes@2.3.0: {} + husky@9.1.7: {} + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} imurmurhash@0.1.4: {} @@ -1047,12 +2047,18 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-property@1.0.2: {} + isexe@2.0.0: {} + jiti@2.6.1: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} keyv@4.5.4: @@ -1094,6 +2100,10 @@ snapshots: strip-ansi: 7.2.0 wrap-ansi: 9.0.2 + long@5.3.2: {} + + lru.min@1.1.4: {} + mimic-function@5.0.1: {} minimatch@10.2.5: @@ -1102,8 +2112,34 @@ snapshots: ms@2.1.3: {} + mysql2@3.15.3: + dependencies: + aws-ssl-profiles: 1.1.2 + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.7.2 + long: 5.3.2 + lru.min: 1.1.4 + named-placeholders: 1.1.6 + seq-queue: 0.0.5 + sqlstring: 2.3.3 + + named-placeholders@1.1.6: + dependencies: + lru.min: 1.1.4 + natural-compare@1.4.0: {} + node-fetch-native@1.6.7: {} + + nypm@0.6.5: + dependencies: + citty: 0.2.2 + pathe: 2.0.3 + tinyexec: 1.0.4 + + ohash@2.0.11: {} + onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -1129,29 +2165,96 @@ snapshots: path-key@3.1.1: {} + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + picomatch@4.0.4: {} + pkg-types@2.3.0: + dependencies: + confbox: 0.2.4 + exsolve: 1.0.8 + pathe: 2.0.3 + + postgres@3.4.7: {} + prelude-ls@1.2.1: {} prettier@3.8.1: {} + prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + "@prisma/config": 7.6.0 + "@prisma/dev": 0.24.3 + "@prisma/engines": 7.6.0 + "@prisma/studio-core": 0.27.3(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + mysql2: 3.15.3 + postgres: 3.4.7 + transitivePeerDependencies: + - "@types/react" + - "@types/react-dom" + - magicast + - react + - react-dom + + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + punycode@2.3.1: {} + pure-rand@6.1.0: {} + + rc9@2.1.2: + dependencies: + defu: 6.1.6 + destr: 2.0.5 + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react@19.2.4: {} + + readdirp@4.1.2: {} + + remeda@2.33.4: {} + + require-from-string@2.0.2: {} + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 signal-exit: 4.1.0 + retry@0.12.0: {} + rfdc@1.4.1: {} + safer-buffer@2.1.2: {} + + scheduler@0.27.0: {} + + seq-queue@0.0.5: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + signal-exit@3.0.7: {} + signal-exit@4.1.0: {} + sisteransi@1.0.5: {} + slice-ansi@7.1.2: dependencies: ansi-styles: 6.2.3 @@ -1162,6 +2265,10 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + sqlstring@2.3.3: {} + + std-env@3.10.0: {} + string-argv@0.3.2: {} string-width@7.2.0: @@ -1198,6 +2305,8 @@ snapshots: dependencies: punycode: 2.3.1 + valibot@1.2.0: {} + which@2.0.2: dependencies: isexe: 2.0.0 @@ -1213,3 +2322,8 @@ snapshots: yaml@2.8.3: {} yocto-queue@0.1.0: {} + + zeptomatch@2.1.0: + dependencies: + grammex: 3.1.12 + graphmatch: 1.1.1 From ae70bd2d75d16cd56d2ace6b47934a9760663fc0 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 12:59:42 +0800 Subject: [PATCH 12/80] feat(api-db): add user auth and security models --- apps/api/prisma/schema.prisma | 72 +++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 8bddec1..a9d3b67 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -11,3 +11,75 @@ generator client { datasource db { provider = "postgresql" } + +enum UserStatus { + ACTIVE + DISABLED + BANNED +} + +enum AuthProvider { + EMAIL + GITHUB + QQ + WECHAT +} + +model User { + id String @id @default(cuid()) + email String @unique + nickname String? + avatarUrl String? + status UserStatus @default(ACTIVE) + defaultStorageQuotaMb Int @default(100) + usedStorageBytes BigInt @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + identities AuthIdentity[] + refreshTokens RefreshToken[] + security UserSecurity? + + @@map("users") +} + +model AuthIdentity { + id String @id @default(cuid()) + userId String + provider AuthProvider + providerUserId String + email String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([provider, providerUserId]) + @@index([userId]) + @@map("auth_identities") +} + +model UserSecurity { + id String @id @default(cuid()) + userId String @unique + twoFactorEnabled Boolean @default(false) + twoFactorSecret String? + recoveryCodes String[] @default([]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@map("user_security") +} + +model RefreshToken { + id String @id @default(cuid()) + userId String + tokenHash String @unique + deviceId String? + expiresAt DateTime + revokedAt DateTime? + createdAt DateTime @default(now()) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId, expiresAt]) + @@map("refresh_tokens") +} From 96cbb8ab302aaa63b7be5000a225b024edbfb3ab Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 13:01:39 +0800 Subject: [PATCH 13/80] feat(api-db): add task attachment and sync models --- apps/api/prisma/schema.prisma | 179 +++++++++++++++++++++++++++++++--- 1 file changed, 168 insertions(+), 11 deletions(-) diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index a9d3b67..1c02f47 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -1,7 +1,4 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -// Get a free hosted Postgres database in seconds: `npx create-db` +// Prisma 数据模型定义(TodoList) generator client { provider = "prisma-client" @@ -25,19 +22,47 @@ enum AuthProvider { WECHAT } +enum TaskPriority { + LOW + MEDIUM + HIGH + URGENT +} + +enum TaskStatus { + TODO + IN_PROGRESS + DONE + ARCHIVED +} + +enum AttachmentType { + IMAGE + VIDEO + FILE + LINK +} + model User { - id String @id @default(cuid()) - email String @unique + id String @id @default(cuid()) + email String @unique nickname String? avatarUrl String? - status UserStatus @default(ACTIVE) - defaultStorageQuotaMb Int @default(100) - usedStorageBytes BigInt @default(0) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + status UserStatus @default(ACTIVE) + defaultStorageQuotaMb Int @default(100) + usedStorageBytes BigInt @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt identities AuthIdentity[] refreshTokens RefreshToken[] security UserSecurity? + tasks Task[] + tags Tag[] + attachments Attachment[] + taskActivityLogs TaskActivityLog[] + syncOperations SyncOperation[] + syncCursors SyncCursor[] + taskTombstones TaskTombstone[] @@map("users") } @@ -83,3 +108,135 @@ model RefreshToken { @@index([userId, expiresAt]) @@map("refresh_tokens") } + +model Task { + id String @id @default(cuid()) + userId String + title String + contentJson Json? + contentText String? + priority TaskPriority @default(MEDIUM) + status TaskStatus @default(TODO) + ddl DateTime? + completedAt DateTime? + version Int @default(1) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + taskTags TaskTag[] + attachments Attachment[] + activityLogs TaskActivityLog[] + + @@index([userId, status]) + @@index([userId, ddl]) + @@map("tasks") +} + +model Tag { + id String @id @default(cuid()) + userId String + name String + color String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + taskTags TaskTag[] + + @@unique([userId, name]) + @@index([userId]) + @@map("tags") +} + +model TaskTag { + taskId String + tagId String + createdAt DateTime @default(now()) + task Task @relation(fields: [taskId], references: [id], onDelete: Cascade) + tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) + + @@id([taskId, tagId]) + @@index([tagId]) + @@map("task_tags") +} + +model Attachment { + id String @id @default(cuid()) + userId String + taskId String? + type AttachmentType + url String + mimeType String? + fileName String? + fileSize Int + width Int? + height Int? + durationMs Int? + checksum String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + task Task? @relation(fields: [taskId], references: [id], onDelete: SetNull) + + @@index([userId]) + @@index([taskId]) + @@map("attachments") +} + +model TaskActivityLog { + id String @id @default(cuid()) + userId String + taskId String + action String + payload Json? + createdAt DateTime @default(now()) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + task Task @relation(fields: [taskId], references: [id], onDelete: Cascade) + + @@index([taskId, createdAt]) + @@index([userId, createdAt]) + @@map("task_activity_logs") +} + +model SyncOperation { + id String @id @default(cuid()) + opId String @unique + userId String + deviceId String + entityType String + entityId String + action String + payload Json? + clientTs DateTime + serverTs DateTime @default(now()) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId, deviceId, serverTs]) + @@index([userId, entityType, entityId]) + @@map("sync_operations") +} + +model SyncCursor { + id String @id @default(cuid()) + userId String + deviceId String + lastPulledAt DateTime? + lastOperationServerTs DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([userId, deviceId]) + @@map("sync_cursors") +} + +model TaskTombstone { + id String @id @default(cuid()) + taskId String @unique + userId String + deletedAt DateTime @default(now()) + deleteOpId String? + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId, deletedAt]) + @@map("task_tombstones") +} From 011fa1f301524a5453de893285507f99558821bf Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 13:04:21 +0800 Subject: [PATCH 14/80] feat(api-db): add ai notification and admin models --- apps/api/prisma.config.ts | 3 +- apps/api/prisma/schema.prisma | 207 ++++++++++++++++++++++++++++++---- 2 files changed, 185 insertions(+), 25 deletions(-) diff --git a/apps/api/prisma.config.ts b/apps/api/prisma.config.ts index 8f3a0eb..d2c3b82 100644 --- a/apps/api/prisma.config.ts +++ b/apps/api/prisma.config.ts @@ -1,5 +1,4 @@ -// This file was generated by Prisma, and assumes you have installed the following: -// npm install --save-dev prisma dotenv +// Prisma CLI 配置(TodoList) import "dotenv/config"; import { defineConfig } from "prisma/config"; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 1c02f47..838df48 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -43,16 +43,34 @@ enum AttachmentType { LINK } +enum AiChannel { + USER_KEY + ASTRBOT + PUBLIC_POOL +} + +enum NotificationChannel { + EMAIL + WEB_PUSH +} + +enum NotificationStatus { + PENDING + SENT + FAILED + CANCELED +} + model User { - id String @id @default(cuid()) - email String @unique + id String @id @default(cuid()) + email String @unique nickname String? avatarUrl String? - status UserStatus @default(ACTIVE) - defaultStorageQuotaMb Int @default(100) - usedStorageBytes BigInt @default(0) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + status UserStatus @default(ACTIVE) + defaultStorageQuotaMb Int @default(100) + usedStorageBytes BigInt @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt identities AuthIdentity[] refreshTokens RefreshToken[] security UserSecurity? @@ -63,6 +81,12 @@ model User { syncOperations SyncOperation[] syncCursors SyncCursor[] taskTombstones TaskTombstone[] + aiProviderBindings AiProviderBinding[] + aiUsageLogs AiUsageLog[] + notificationRules NotificationRule[] + notificationJobs NotificationJob[] + createdAdminTokens AdminToken[] + auditLogs AuditLog[] @@map("users") } @@ -110,22 +134,24 @@ model RefreshToken { } model Task { - id String @id @default(cuid()) - userId String - title String - contentJson Json? - contentText String? - priority TaskPriority @default(MEDIUM) - status TaskStatus @default(TODO) - ddl DateTime? - completedAt DateTime? - version Int @default(1) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - taskTags TaskTag[] - attachments Attachment[] - activityLogs TaskActivityLog[] + id String @id @default(cuid()) + userId String + title String + contentJson Json? + contentText String? + priority TaskPriority @default(MEDIUM) + status TaskStatus @default(TODO) + ddl DateTime? + completedAt DateTime? + version Int @default(1) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + taskTags TaskTag[] + attachments Attachment[] + activityLogs TaskActivityLog[] + notificationJobs NotificationJob[] + notificationRules NotificationRule[] @@index([userId, status]) @@index([userId, ddl]) @@ -240,3 +266,138 @@ model TaskTombstone { @@index([userId, deletedAt]) @@map("task_tombstones") } + +model AiProviderBinding { + id String @id @default(cuid()) + userId String + channel AiChannel + providerName String + model String? + encryptedApiKey String? + endpoint String? + isDefault Boolean @default(false) + isEnabled Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId, isEnabled]) + @@map("ai_provider_bindings") +} + +model AiPublicPoolConfig { + id String @id @default(cuid()) + enabled Boolean @default(false) + providerName String? + model String? + encryptedApiKey String? + endpoint String? + rpmLimit Int @default(60) + dailyTokenLimit Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("ai_public_pool_config") +} + +model AiUsageLog { + id String @id @default(cuid()) + userId String? + channel AiChannel + providerName String? + model String? + promptTokens Int @default(0) + completionTokens Int @default(0) + totalTokens Int @default(0) + latencyMs Int? + success Boolean @default(true) + errorCode String? + createdAt DateTime @default(now()) + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + + @@index([userId, createdAt]) + @@index([channel, createdAt]) + @@map("ai_usage_logs") +} + +model NotificationRule { + id String @id @default(cuid()) + userId String + taskId String? + channel NotificationChannel @default(EMAIL) + advanceMinutes Int @default(60) + enabled Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + task Task? @relation(fields: [taskId], references: [id], onDelete: SetNull) + jobs NotificationJob[] + + @@index([userId, enabled]) + @@index([taskId]) + @@map("notification_rules") +} + +model NotificationJob { + id String @id @default(cuid()) + userId String + taskId String? + ruleId String? + channel NotificationChannel + scheduledAt DateTime + sentAt DateTime? + status NotificationStatus @default(PENDING) + retryCount Int @default(0) + errorMessage String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + task Task? @relation(fields: [taskId], references: [id], onDelete: SetNull) + rule NotificationRule? @relation(fields: [ruleId], references: [id], onDelete: SetNull) + + @@index([status, scheduledAt]) + @@index([userId, createdAt]) + @@map("notification_jobs") +} + +model SystemSetting { + id String @id @default(cuid()) + key String @unique + value Json + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("system_settings") +} + +model AdminToken { + id String @id @default(cuid()) + tokenHash String @unique + name String + expiresAt DateTime + lastUsedAt DateTime? + revokedAt DateTime? + createdAt DateTime @default(now()) + createdByUserId String? + createdByUser User? @relation(fields: [createdByUserId], references: [id], onDelete: SetNull) + + @@index([expiresAt]) + @@map("admin_tokens") +} + +model AuditLog { + id String @id @default(cuid()) + actorUserId String? + action String + targetType String + targetId String? + meta Json? + ip String? + userAgent String? + createdAt DateTime @default(now()) + actorUser User? @relation(fields: [actorUserId], references: [id], onDelete: SetNull) + + @@index([action, createdAt]) + @@index([actorUserId, createdAt]) + @@map("audit_logs") +} From 9bd5474401b32e1e2744ca02d35df70bad60dcf9 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 13:06:07 +0800 Subject: [PATCH 15/80] chore(api-db): localize prisma ignore comment --- apps/api/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/.gitignore b/apps/api/.gitignore index 9f62ec0..b4c678e 100644 --- a/apps/api/.gitignore +++ b/apps/api/.gitignore @@ -1,5 +1,5 @@ node_modules -# Keep environment variables out of version control +# 环境变量文件不纳入版本控制 .env /generated/prisma From 5d650e00f6e1d23c34c704e3b3b70688c2d63832 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 14:32:55 +0800 Subject: [PATCH 16/80] fix(api-db): add node type definitions for prisma config --- apps/api/package.json | 1 + pnpm-lock.yaml | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/apps/api/package.json b/apps/api/package.json index fd3daf9..900605c 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -9,6 +9,7 @@ }, "license": "GPL-3.0-or-later", "devDependencies": { + "@types/node": "^25.5.2", "dotenv": "^16.6.1", "prisma": "^7.6.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48e6d7f..7e2a3fb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: specifier: ^7.6.0 version: 7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) devDependencies: + "@types/node": + specifier: ^25.5.2 + version: 25.5.2 dotenv: specifier: ^16.6.1 version: 16.6.1 @@ -457,6 +460,12 @@ packages: integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== } + "@types/node@25.5.2": + resolution: + { + integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg== + } + "@types/react@19.2.14": resolution: { @@ -1464,6 +1473,12 @@ packages: } engines: { node: ">= 0.8.0" } + undici-types@7.18.2: + resolution: + { + integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== + } + uri-js@4.4.1: resolution: { @@ -1762,6 +1777,10 @@ snapshots: "@types/json-schema@7.0.15": {} + "@types/node@25.5.2": + dependencies: + undici-types: 7.18.2 + "@types/react@19.2.14": dependencies: csstype: 3.2.3 @@ -2301,6 +2320,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 + undici-types@7.18.2: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 From efe55fdc2f40e81722d2fb97e4c546745822499f Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 21:02:17 +0800 Subject: [PATCH 17/80] feat(api-auth): implement email code send and login --- apps/api/.env.example | 5 +- apps/api/.gitignore | 3 + apps/api/package.json | 23 +- apps/api/src/app.module.ts | 14 + apps/api/src/auth/auth.controller.ts | 28 + apps/api/src/auth/auth.module.ts | 27 + apps/api/src/auth/auth.service.ts | 104 + apps/api/src/auth/dto/email-login.dto.ts | 11 + apps/api/src/auth/dto/send-email-code.dto.ts | 6 + apps/api/src/main.ts | 19 + apps/api/tsconfig.build.json | 5 + apps/api/tsconfig.json | 10 + pnpm-lock.yaml | 2040 +++++++++++++++++- 13 files changed, 2282 insertions(+), 13 deletions(-) create mode 100644 apps/api/src/app.module.ts create mode 100644 apps/api/src/auth/auth.controller.ts create mode 100644 apps/api/src/auth/auth.module.ts create mode 100644 apps/api/src/auth/auth.service.ts create mode 100644 apps/api/src/auth/dto/email-login.dto.ts create mode 100644 apps/api/src/auth/dto/send-email-code.dto.ts create mode 100644 apps/api/src/main.ts create mode 100644 apps/api/tsconfig.build.json create mode 100644 apps/api/tsconfig.json diff --git a/apps/api/.env.example b/apps/api/.env.example index 203f3a0..6406c52 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -1 +1,4 @@ -DATABASE_URL="postgresql://postgres:postgres@localhost:5432/todolist?schema=public" +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/todolist?schema=public" +AUTH_ACCESS_SECRET="dev-access-secret" +AUTH_ACCESS_EXPIRES_IN_SECONDS="900" +AUTH_EMAIL_CODE_TTL_SECONDS="300" diff --git a/apps/api/.gitignore b/apps/api/.gitignore index b4c678e..13b7b20 100644 --- a/apps/api/.gitignore +++ b/apps/api/.gitignore @@ -3,3 +3,6 @@ node_modules .env /generated/prisma +dist +prisma.config.js +prisma.config.js.map diff --git a/apps/api/package.json b/apps/api/package.json index 900605c..e02e42d 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -5,16 +5,33 @@ "scripts": { "prisma:generate": "prisma generate", "prisma:format": "prisma format", - "prisma:validate": "prisma validate" + "prisma:validate": "prisma validate", + "start": "node dist/main.js", + "start:dev": "ts-node-dev --respawn --transpile-only src/main.ts", + "build": "tsc -p tsconfig.build.json", + "typecheck": "tsc --noEmit -p tsconfig.json", + "test": "echo api tests pending" }, "license": "GPL-3.0-or-later", "devDependencies": { "@types/node": "^25.5.2", "dotenv": "^16.6.1", - "prisma": "^7.6.0" + "prisma": "^7.6.0", + "ts-node": "^10.9.2", + "ts-node-dev": "^2.0.0", + "typescript": "^5.9.3" }, "private": true, "dependencies": { - "@prisma/client": "^7.6.0" + "@nestjs/common": "^11.1.18", + "@nestjs/config": "^4.0.3", + "@nestjs/core": "^11.1.18", + "@nestjs/jwt": "^11.0.2", + "@nestjs/platform-express": "^11.1.18", + "@prisma/client": "^7.6.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.15.1", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.2" } } diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts new file mode 100644 index 0000000..8cd6181 --- /dev/null +++ b/apps/api/src/app.module.ts @@ -0,0 +1,14 @@ +import { Module } from "@nestjs/common"; +import { ConfigModule } from "@nestjs/config"; +import { AuthModule } from "./auth/auth.module"; + +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + envFilePath: ".env" + }), + AuthModule + ] +}) +export class AppModule {} diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts new file mode 100644 index 0000000..6708a53 --- /dev/null +++ b/apps/api/src/auth/auth.controller.ts @@ -0,0 +1,28 @@ +import { Body, Controller, Post } from "@nestjs/common"; +import { AuthService } from "./auth.service"; +import { EmailLoginDto } from "./dto/email-login.dto"; +import { SendEmailCodeDto } from "./dto/send-email-code.dto"; + +@Controller("auth") +export class AuthController { + constructor(private readonly authService: AuthService) {} + + @Post("email/send-code") + async sendEmailCode( + @Body() body: SendEmailCodeDto + ): Promise<{ success: boolean; expiresInSeconds: number; debugCode: string }> { + return this.authService.sendEmailCode(body.email); + } + + @Post("email/login") + async loginWithEmailCode( + @Body() body: EmailLoginDto + ): Promise<{ + accessToken: string; + tokenType: "Bearer"; + expiresInSeconds: number; + user: { id: string; email: string }; + }> { + return this.authService.loginWithEmailCode(body.email, body.code); + } +} diff --git a/apps/api/src/auth/auth.module.ts b/apps/api/src/auth/auth.module.ts new file mode 100644 index 0000000..f8b73ce --- /dev/null +++ b/apps/api/src/auth/auth.module.ts @@ -0,0 +1,27 @@ +import { Module } from "@nestjs/common"; +import { ConfigModule, ConfigService } from "@nestjs/config"; +import { JwtModule } from "@nestjs/jwt"; +import { AuthController } from "./auth.controller"; +import { AuthService } from "./auth.service"; + +@Module({ + imports: [ + ConfigModule, + JwtModule.registerAsync({ + inject: [ConfigService], + useFactory: (configService: ConfigService) => { + const expiresInSeconds = Number(configService.get("AUTH_ACCESS_EXPIRES_IN_SECONDS") ?? 900); + + return { + secret: configService.get("AUTH_ACCESS_SECRET") ?? "dev-access-secret", + signOptions: { + expiresIn: expiresInSeconds + } + }; + } + }) + ], + controllers: [AuthController], + providers: [AuthService] +}) +export class AuthModule {} diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts new file mode 100644 index 0000000..9ede99c --- /dev/null +++ b/apps/api/src/auth/auth.service.ts @@ -0,0 +1,104 @@ +import { Injectable, UnauthorizedException } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { JwtService } from "@nestjs/jwt"; +import { randomUUID } from "node:crypto"; + +type EmailCodeEntry = { + code: string; + expiresAt: number; +}; + +type AuthUser = { + id: string; + email: string; +}; + +@Injectable() +export class AuthService { + private readonly emailCodeStore = new Map(); + private readonly userStore = new Map(); + + constructor( + private readonly configService: ConfigService, + private readonly jwtService: JwtService + ) {} + + async sendEmailCode( + email: string + ): Promise<{ success: boolean; expiresInSeconds: number; debugCode: string }> { + const ttlSeconds = Number(this.configService.get("AUTH_EMAIL_CODE_TTL_SECONDS") ?? 300); + const code = this.generateCode(); + const expiresAt = Date.now() + ttlSeconds * 1000; + + this.emailCodeStore.set(email.toLowerCase(), { code, expiresAt }); + + return { + success: true, + expiresInSeconds: ttlSeconds, + debugCode: code + }; + } + + async loginWithEmailCode( + email: string, + code: string + ): Promise<{ + accessToken: string; + tokenType: "Bearer"; + expiresInSeconds: number; + user: AuthUser; + }> { + const lowerEmail = email.toLowerCase(); + const codeEntry = this.emailCodeStore.get(lowerEmail); + + if (!codeEntry) { + throw new UnauthorizedException("验证码不存在或已失效"); + } + + if (codeEntry.expiresAt < Date.now()) { + this.emailCodeStore.delete(lowerEmail); + throw new UnauthorizedException("验证码已过期"); + } + + if (codeEntry.code !== code) { + throw new UnauthorizedException("验证码错误"); + } + + this.emailCodeStore.delete(lowerEmail); + + const user = this.getOrCreateUser(lowerEmail); + const expiresInSeconds = Number( + this.configService.get("AUTH_ACCESS_EXPIRES_IN_SECONDS") ?? 900 + ); + const accessToken = await this.jwtService.signAsync({ + sub: user.id, + email: user.email + }); + + return { + accessToken, + tokenType: "Bearer", + expiresInSeconds, + user + }; + } + + private getOrCreateUser(email: string): AuthUser { + const existingUser = this.userStore.get(email); + if (existingUser) { + return existingUser; + } + + const newUser = { + id: randomUUID(), + email + }; + this.userStore.set(email, newUser); + + return newUser; + } + + private generateCode(): string { + return String(Math.floor(100000 + Math.random() * 900000)); + } +} diff --git a/apps/api/src/auth/dto/email-login.dto.ts b/apps/api/src/auth/dto/email-login.dto.ts new file mode 100644 index 0000000..32b1511 --- /dev/null +++ b/apps/api/src/auth/dto/email-login.dto.ts @@ -0,0 +1,11 @@ +import { IsEmail, IsString, Length, Matches } from "class-validator"; + +export class EmailLoginDto { + @IsEmail() + email!: string; + + @IsString() + @Length(6, 6) + @Matches(/^\d{6}$/) + code!: string; +} diff --git a/apps/api/src/auth/dto/send-email-code.dto.ts b/apps/api/src/auth/dto/send-email-code.dto.ts new file mode 100644 index 0000000..998b618 --- /dev/null +++ b/apps/api/src/auth/dto/send-email-code.dto.ts @@ -0,0 +1,6 @@ +import { IsEmail } from "class-validator"; + +export class SendEmailCodeDto { + @IsEmail() + email!: string; +} diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts new file mode 100644 index 0000000..c7aef94 --- /dev/null +++ b/apps/api/src/main.ts @@ -0,0 +1,19 @@ +import "reflect-metadata"; +import { ValidationPipe } from "@nestjs/common"; +import { NestFactory } from "@nestjs/core"; +import { AppModule } from "./app.module"; + +async function bootstrap(): Promise { + const app = await NestFactory.create(AppModule); + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + whitelist: true, + forbidNonWhitelisted: true + }) + ); + + await app.listen(3000); +} + +void bootstrap(); diff --git a/apps/api/tsconfig.build.json b/apps/api/tsconfig.build.json new file mode 100644 index 0000000..10b2c04 --- /dev/null +++ b/apps/api/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "exclude": ["node_modules", "dist", "**/*.spec.ts"] +} diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 0000000..a6aded2 --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../packages/tsconfig/nest-app.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e2a3fb..6c8607a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,9 +31,36 @@ importers: apps/api: dependencies: + "@nestjs/common": + specifier: ^11.1.18 + version: 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + "@nestjs/config": + specifier: ^4.0.3 + version: 4.0.3(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + "@nestjs/core": + specifier: ^11.1.18 + version: 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + "@nestjs/jwt": + specifier: ^11.0.2 + version: 11.0.2(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)) + "@nestjs/platform-express": + specifier: ^11.1.18 + version: 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18) "@prisma/client": specifier: ^7.6.0 - version: 7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + version: 7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.15.1 + version: 0.15.1 + reflect-metadata: + specifier: ^0.2.2 + version: 0.2.2 + rxjs: + specifier: ^7.8.2 + version: 7.8.2 devDependencies: "@types/node": specifier: ^25.5.2 @@ -43,13 +70,28 @@ importers: version: 16.6.1 prisma: specifier: ^7.6.0 - version: 7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@25.5.2)(typescript@5.9.3) + ts-node-dev: + specifier: ^2.0.0 + version: 2.0.0(@types/node@25.5.2)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 packages/eslint-config: {} packages/tsconfig: {} packages: + "@borewit/text-codec@0.2.2": + resolution: + { + integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ== + } + "@clack/core@0.5.0": resolution: { @@ -62,6 +104,13 @@ packages: integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw== } + "@cspotcode/source-map-support@0.8.1": + resolution: + { + integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + } + engines: { node: ">=12" } + "@electric-sql/pglite-socket@0.1.1": resolution: { @@ -185,12 +234,109 @@ packages: } engines: { node: ">=18.18" } + "@jridgewell/resolve-uri@3.1.2": + resolution: + { + integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + } + engines: { node: ">=6.0.0" } + + "@jridgewell/sourcemap-codec@1.5.5": + resolution: + { + integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + } + + "@jridgewell/trace-mapping@0.3.9": + resolution: + { + integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + } + "@kurkle/color@0.3.4": resolution: { integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w== } + "@lukeed/csprng@1.1.0": + resolution: + { + integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== + } + engines: { node: ">=8" } + + "@nestjs/common@11.1.18": + resolution: + { + integrity: sha512-0sLq8Z+TIjLnz1Tqp0C/x9BpLbqpt1qEu0VcH4/fkE0y3F5JxhfK1AdKQ/SPbKhKgwqVDoY4gS8GQr2G6ujaWg== + } + peerDependencies: + class-transformer: ">=0.4.1" + class-validator: ">=0.13.2" + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + + "@nestjs/config@4.0.3": + resolution: + { + integrity: sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ== + } + peerDependencies: + "@nestjs/common": ^10.0.0 || ^11.0.0 + rxjs: ^7.1.0 + + "@nestjs/core@11.1.18": + resolution: + { + integrity: sha512-wR3DtGyk/LUAiPtbXDuWJJwVkWElKBY0sqnTzf9d4uM3+X18FRZhK7WFc47czsIGOdWuRsMeLYV+1Z9dO4zDEQ== + } + engines: { node: ">= 20" } + peerDependencies: + "@nestjs/common": ^11.0.0 + "@nestjs/microservices": ^11.0.0 + "@nestjs/platform-express": ^11.0.0 + "@nestjs/websockets": ^11.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + "@nestjs/microservices": + optional: true + "@nestjs/platform-express": + optional: true + "@nestjs/websockets": + optional: true + + "@nestjs/jwt@11.0.2": + resolution: + { + integrity: sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA== + } + peerDependencies: + "@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + + "@nestjs/platform-express@11.1.18": + resolution: + { + integrity: sha512-s6GdHMTa3qx0fJewR74Xa30ysPHfBEqxIwZ7BGSTLoAEQ1vTP24urNl+b6+s49NFLEIOyeNho5fN/9/I17QlOw== + } + peerDependencies: + "@nestjs/common": ^11.0.0 + "@nestjs/core": ^11.0.0 + + "@nuxt/opencollective@0.4.1": + resolution: + { + integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ== + } + engines: { node: ^14.18.0 || >=16.10.0, npm: ">=5.10.0" } + hasBin: true + "@prisma/client-runtime-utils@7.6.0": resolution: { @@ -394,6 +540,43 @@ packages: integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== } + "@tokenizer/inflate@0.4.1": + resolution: + { + integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA== + } + engines: { node: ">=18" } + + "@tokenizer/token@0.3.0": + resolution: + { + integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + } + + "@tsconfig/node10@1.0.12": + resolution: + { + integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ== + } + + "@tsconfig/node12@1.0.11": + resolution: + { + integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + } + + "@tsconfig/node14@1.0.3": + resolution: + { + integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + } + + "@tsconfig/node16@1.0.4": + resolution: + { + integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + } + "@turbo/darwin-64@2.9.3": resolution: { @@ -460,6 +643,18 @@ packages: integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== } + "@types/jsonwebtoken@9.0.10": + resolution: + { + integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA== + } + + "@types/ms@2.1.0": + resolution: + { + integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + } + "@types/node@25.5.2": resolution: { @@ -472,6 +667,31 @@ packages: integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== } + "@types/strip-bom@3.0.0": + resolution: + { + integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ== + } + + "@types/strip-json-comments@0.0.30": + resolution: + { + integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== + } + + "@types/validator@13.15.10": + resolution: + { + integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA== + } + + accepts@2.0.0: + resolution: + { + integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + } + engines: { node: ">= 0.6" } + acorn-jsx@5.3.2: resolution: { @@ -480,6 +700,13 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.5: + resolution: + { + integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw== + } + engines: { node: ">=0.4.0" } + acorn@8.16.0: resolution: { @@ -521,6 +748,25 @@ packages: } engines: { node: ">=12" } + anymatch@3.1.3: + resolution: + { + integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + } + engines: { node: ">= 8" } + + append-field@1.0.0: + resolution: + { + integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== + } + + arg@4.1.3: + resolution: + { + integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + } + aws-ssl-profiles@1.1.2: resolution: { @@ -528,6 +774,12 @@ packages: } engines: { node: ">= 6.0.0" } + balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + } + balanced-match@4.0.4: resolution: { @@ -542,6 +794,26 @@ packages: } hasBin: true + binary-extensions@2.3.0: + resolution: + { + integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + } + engines: { node: ">=8" } + + body-parser@2.2.2: + resolution: + { + integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA== + } + engines: { node: ">=18" } + + brace-expansion@1.1.13: + resolution: + { + integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w== + } + brace-expansion@5.0.5: resolution: { @@ -549,6 +821,39 @@ packages: } engines: { node: 18 || 20 || >=22 } + braces@3.0.3: + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + } + engines: { node: ">=8" } + + buffer-equal-constant-time@1.0.1: + resolution: + { + integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + } + + buffer-from@1.1.2: + resolution: + { + integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + } + + busboy@1.6.0: + resolution: + { + integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + } + engines: { node: ">=10.16.0" } + + bytes@3.1.2: + resolution: + { + integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + } + engines: { node: ">= 0.8" } + c12@3.1.0: resolution: { @@ -560,6 +865,20 @@ packages: magicast: optional: true + call-bind-apply-helpers@1.0.2: + resolution: + { + integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + } + engines: { node: ">= 0.4" } + + call-bound@1.0.4: + resolution: + { + integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + } + engines: { node: ">= 0.4" } + chart.js@4.5.1: resolution: { @@ -567,6 +886,13 @@ packages: } engines: { pnpm: ">=8" } + chokidar@3.6.0: + resolution: + { + integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + } + engines: { node: ">= 8.10.0" } + chokidar@4.0.3: resolution: { @@ -586,6 +912,18 @@ packages: integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w== } + class-transformer@0.5.1: + resolution: + { + integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== + } + + class-validator@0.15.1: + resolution: + { + integrity: sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw== + } + cli-cursor@5.0.0: resolution: { @@ -613,6 +951,19 @@ packages: } engines: { node: ">=20" } + concat-map@0.0.1: + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + } + + concat-stream@2.0.0: + resolution: + { + integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== + } + engines: { "0": node >= 6.0 } + confbox@0.2.4: resolution: { @@ -626,6 +977,47 @@ packages: } engines: { node: ^14.18.0 || >=16.10.0 } + content-disposition@1.0.1: + resolution: + { + integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q== + } + engines: { node: ">=18" } + + content-type@1.0.5: + resolution: + { + integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + } + engines: { node: ">= 0.6" } + + cookie-signature@1.2.2: + resolution: + { + integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + } + engines: { node: ">=6.6.0" } + + cookie@0.7.2: + resolution: + { + integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + } + engines: { node: ">= 0.6" } + + cors@2.8.6: + resolution: + { + integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw== + } + engines: { node: ">= 0.10" } + + create-require@1.1.1: + resolution: + { + integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + } + cross-spawn@7.0.6: resolution: { @@ -677,12 +1069,33 @@ packages: } engines: { node: ">=0.10" } + depd@2.0.0: + resolution: + { + integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + } + engines: { node: ">= 0.8" } + destr@2.0.5: resolution: { integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA== } + diff@4.0.4: + resolution: + { + integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ== + } + engines: { node: ">=0.3.1" } + + dotenv-expand@12.0.3: + resolution: + { + integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA== + } + engines: { node: ">=12" } + dotenv@16.6.1: resolution: { @@ -690,6 +1103,38 @@ packages: } engines: { node: ">=12" } + dotenv@17.2.3: + resolution: + { + integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w== + } + engines: { node: ">=12" } + + dunder-proto@1.0.1: + resolution: + { + integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + } + engines: { node: ">= 0.4" } + + dynamic-dedupe@0.3.0: + resolution: + { + integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ== + } + + ecdsa-sig-formatter@1.0.11: + resolution: + { + integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + } + + ee-first@1.1.1: + resolution: + { + integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + } + effect@3.20.0: resolution: { @@ -709,6 +1154,13 @@ packages: } engines: { node: ">=14" } + encodeurl@2.0.0: + resolution: + { + integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + } + engines: { node: ">= 0.8" } + env-paths@3.0.0: resolution: { @@ -723,6 +1175,33 @@ packages: } engines: { node: ">=18" } + es-define-property@1.0.1: + resolution: + { + integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + } + engines: { node: ">= 0.4" } + + es-errors@1.3.0: + resolution: + { + integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + } + engines: { node: ">= 0.4" } + + es-object-atoms@1.1.1: + resolution: + { + integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + } + engines: { node: ">= 0.4" } + + escape-html@1.0.3: + resolution: + { + integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + } + escape-string-regexp@4.0.0: resolution: { @@ -799,12 +1278,26 @@ packages: } engines: { node: ">=0.10.0" } + etag@1.8.1: + resolution: + { + integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + } + engines: { node: ">= 0.6" } + eventemitter3@5.0.4: resolution: { integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== } + express@5.2.1: + resolution: + { + integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== + } + engines: { node: ">= 18" } + exsolve@1.0.8: resolution: { @@ -836,6 +1329,12 @@ packages: integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== } + fast-safe-stringify@2.1.1: + resolution: + { + integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + } + fast-uri@3.1.0: resolution: { @@ -849,6 +1348,27 @@ packages: } engines: { node: ">=16.0.0" } + file-type@21.3.4: + resolution: + { + integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g== + } + engines: { node: ">=20" } + + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + } + engines: { node: ">=8" } + + finalhandler@2.1.1: + resolution: + { + integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA== + } + engines: { node: ">= 18.0.0" } + find-up@5.0.0: resolution: { @@ -876,6 +1396,40 @@ packages: } engines: { node: ">=14" } + forwarded@0.2.0: + resolution: + { + integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + } + engines: { node: ">= 0.6" } + + fresh@2.0.0: + resolution: + { + integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + } + engines: { node: ">= 0.8" } + + fs.realpath@1.0.0: + resolution: + { + integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + } + + fsevents@2.3.3: + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + function-bind@1.1.2: + resolution: + { + integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + } + generate-function@2.3.1: resolution: { @@ -889,12 +1443,26 @@ packages: } engines: { node: ">=18" } + get-intrinsic@1.3.0: + resolution: + { + integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + } + engines: { node: ">= 0.4" } + get-port-please@3.2.0: resolution: { integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A== } + get-proto@1.0.1: + resolution: + { + integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + } + engines: { node: ">= 0.4" } + giget@2.0.0: resolution: { @@ -902,6 +1470,13 @@ packages: } hasBin: true + glob-parent@5.1.2: + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + } + engines: { node: ">= 6" } + glob-parent@6.0.2: resolution: { @@ -909,6 +1484,13 @@ packages: } engines: { node: ">=10.13.0" } + glob@7.2.3: + resolution: + { + 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 + globals@17.4.0: resolution: { @@ -916,6 +1498,13 @@ packages: } engines: { node: ">=18" } + gopd@1.2.0: + resolution: + { + integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + } + engines: { node: ">= 0.4" } + graceful-fs@4.2.11: resolution: { @@ -934,6 +1523,20 @@ packages: integrity: sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg== } + has-symbols@1.1.0: + resolution: + { + integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + } + engines: { node: ">= 0.4" } + + hasown@2.0.2: + resolution: + { + integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + } + engines: { node: ">= 0.4" } + hono@4.12.10: resolution: { @@ -941,6 +1544,13 @@ packages: } engines: { node: ">=16.9.0" } + http-errors@2.0.1: + resolution: + { + integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + } + engines: { node: ">= 0.8" } + http-status-codes@2.3.0: resolution: { @@ -962,6 +1572,12 @@ packages: } engines: { node: ">=0.10.0" } + ieee754@1.2.1: + resolution: + { + integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + } + ignore@5.3.2: resolution: { @@ -976,6 +1592,40 @@ packages: } engines: { node: ">=0.8.19" } + inflight@1.0.6: + resolution: + { + integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + } + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + } + + ipaddr.js@1.9.1: + resolution: + { + integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + } + engines: { node: ">= 0.10" } + + is-binary-path@2.1.0: + resolution: + { + integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + } + engines: { node: ">=8" } + + is-core-module@2.16.1: + resolution: + { + integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + } + engines: { node: ">= 0.4" } + is-extglob@2.1.1: resolution: { @@ -997,6 +1647,19 @@ packages: } engines: { node: ">=0.10.0" } + is-number@7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + } + engines: { node: ">=0.12.0" } + + is-promise@4.0.0: + resolution: + { + integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + } + is-property@1.0.2: resolution: { @@ -1009,6 +1672,13 @@ packages: integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } + iterare@1.2.1: + resolution: + { + integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== + } + engines: { node: ">=6" } + jiti@2.6.1: resolution: { @@ -1040,6 +1710,25 @@ packages: integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== } + jsonwebtoken@9.0.3: + resolution: + { + integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g== + } + engines: { node: ">=12", npm: ">=6" } + + jwa@2.0.1: + resolution: + { + integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg== + } + + jws@4.0.1: + resolution: + { + integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA== + } + keyv@4.5.4: resolution: { @@ -1053,6 +1742,12 @@ packages: } engines: { node: ">= 0.8.0" } + libphonenumber-js@1.12.41: + resolution: + { + integrity: sha512-lsmMmGXBxXIK/VMLEj0kL6MtUs1kBGj1nTCzi6zgQoG1DEwqwt2DQyHxcLykceIxAnfE3hya7NuIh6PpC6S3fA== + } + lint-staged@16.4.0: resolution: { @@ -1068,6 +1763,13 @@ packages: } engines: { node: ">=20.0.0" } + load-esm@1.0.3: + resolution: + { + integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA== + } + engines: { node: ">=13.2.0" } + locate-path@6.0.0: resolution: { @@ -1075,6 +1777,54 @@ packages: } engines: { node: ">=10" } + lodash.includes@4.3.0: + resolution: + { + integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + } + + lodash.isboolean@3.0.3: + resolution: + { + integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + } + + lodash.isinteger@4.0.4: + resolution: + { + integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + } + + lodash.isnumber@3.0.3: + resolution: + { + integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + } + + lodash.isplainobject@4.0.6: + resolution: + { + integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + } + + lodash.isstring@4.0.1: + resolution: + { + integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + } + + lodash.once@4.1.1: + resolution: + { + integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + } + + lodash@4.17.23: + resolution: + { + integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== + } + log-update@6.1.0: resolution: { @@ -1095,6 +1845,68 @@ packages: } engines: { bun: ">=1.0.0", deno: ">=1.30.0", node: ">=8.0.0" } + make-error@1.3.6: + resolution: + { + integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + } + + math-intrinsics@1.1.0: + resolution: + { + integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + } + engines: { node: ">= 0.4" } + + media-typer@0.3.0: + resolution: + { + integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + } + engines: { node: ">= 0.6" } + + media-typer@1.1.0: + resolution: + { + integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + } + engines: { node: ">= 0.8" } + + merge-descriptors@2.0.0: + resolution: + { + integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + } + engines: { node: ">=18" } + + mime-db@1.52.0: + resolution: + { + integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + } + engines: { node: ">= 0.6" } + + mime-db@1.54.0: + resolution: + { + integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + } + engines: { node: ">= 0.6" } + + mime-types@2.1.35: + resolution: + { + integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + } + engines: { node: ">= 0.6" } + + mime-types@3.0.2: + resolution: + { + integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== + } + engines: { node: ">=18" } + mimic-function@5.0.1: resolution: { @@ -1109,12 +1921,39 @@ packages: } engines: { node: 18 || 20 || >=22 } + minimatch@3.1.5: + resolution: + { + integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== + } + + minimist@1.2.8: + resolution: + { + integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + } + + mkdirp@1.0.4: + resolution: + { + integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + } + engines: { node: ">=10" } + hasBin: true + ms@2.1.3: resolution: { integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== } + multer@2.1.1: + resolution: + { + integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A== + } + engines: { node: ">= 10.16.0" } + mysql2@3.15.3: resolution: { @@ -1135,12 +1974,26 @@ packages: integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== } + negotiator@1.0.0: + resolution: + { + integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + } + engines: { node: ">= 0.6" } + node-fetch-native@1.6.7: resolution: { integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q== } + normalize-path@3.0.0: + resolution: + { + integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + } + engines: { node: ">=0.10.0" } + nypm@0.6.5: resolution: { @@ -1149,12 +2002,39 @@ packages: engines: { node: ">=18" } hasBin: true + object-assign@4.1.1: + resolution: + { + integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + } + engines: { node: ">=0.10.0" } + + object-inspect@1.13.4: + resolution: + { + integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + } + engines: { node: ">= 0.4" } + ohash@2.0.11: resolution: { integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ== } + on-finished@2.4.1: + resolution: + { + integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + } + engines: { node: ">= 0.8" } + + once@1.4.0: + resolution: + { + integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + } + onetime@7.0.0: resolution: { @@ -1183,6 +2063,13 @@ packages: } engines: { node: ">=10" } + parseurl@1.3.3: + resolution: + { + integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + } + engines: { node: ">= 0.8" } + path-exists@4.0.0: resolution: { @@ -1190,6 +2077,13 @@ packages: } engines: { node: ">=8" } + path-is-absolute@1.0.1: + resolution: + { + integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + } + engines: { node: ">=0.10.0" } + path-key@3.1.1: resolution: { @@ -1197,6 +2091,18 @@ packages: } engines: { node: ">=8" } + path-parse@1.0.7: + resolution: + { + integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + } + + path-to-regexp@8.4.2: + resolution: + { + integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA== + } + pathe@2.0.3: resolution: { @@ -1215,6 +2121,13 @@ packages: integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== } + picomatch@2.3.2: + resolution: + { + integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== + } + engines: { node: ">=8.6" } + picomatch@4.0.4: resolution: { @@ -1272,6 +2185,13 @@ packages: integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== } + proxy-addr@2.0.7: + resolution: + { + integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + } + engines: { node: ">= 0.10" } + punycode@2.3.1: resolution: { @@ -1285,6 +2205,27 @@ packages: integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== } + qs@6.15.0: + resolution: + { + integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ== + } + engines: { node: ">=0.6" } + + range-parser@1.2.1: + resolution: + { + integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + } + engines: { node: ">= 0.6" } + + raw-body@3.0.2: + resolution: + { + integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== + } + engines: { node: ">= 0.10" } + rc9@2.1.2: resolution: { @@ -1306,6 +2247,20 @@ packages: } engines: { node: ">=0.10.0" } + readable-stream@3.6.2: + resolution: + { + integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + } + engines: { node: ">= 6" } + + readdirp@3.6.0: + resolution: + { + integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + } + engines: { node: ">=8.10.0" } + readdirp@4.1.2: resolution: { @@ -1313,6 +2268,12 @@ packages: } engines: { node: ">= 14.18.0" } + reflect-metadata@0.2.2: + resolution: + { + integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== + } + remeda@2.33.4: resolution: { @@ -1326,6 +2287,14 @@ packages: } engines: { node: ">=0.10.0" } + resolve@1.22.11: + resolution: + { + integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== + } + engines: { node: ">= 0.4" } + hasBin: true + restore-cursor@5.1.0: resolution: { @@ -1346,6 +2315,33 @@ packages: integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== } + rimraf@2.7.1: + resolution: + { + integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + } + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + router@2.2.0: + resolution: + { + integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + } + engines: { node: ">= 18" } + + rxjs@7.8.2: + resolution: + { + integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + } + + safe-buffer@5.2.1: + resolution: + { + integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + } + safer-buffer@2.1.2: resolution: { @@ -1358,12 +2354,40 @@ packages: integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== } + semver@7.7.4: + resolution: + { + integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + } + engines: { node: ">=10" } + hasBin: true + + send@1.2.1: + resolution: + { + integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ== + } + engines: { node: ">= 18" } + seq-queue@0.0.5: resolution: { integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q== } + serve-static@2.2.1: + resolution: + { + integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw== + } + engines: { node: ">= 18" } + + setprototypeof@1.2.0: + resolution: + { + integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + } + shebang-command@2.0.0: resolution: { @@ -1378,6 +2402,34 @@ packages: } engines: { node: ">=8" } + side-channel-list@1.0.0: + resolution: + { + integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + } + engines: { node: ">= 0.4" } + + side-channel-map@1.0.1: + resolution: + { + integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + } + engines: { node: ">= 0.4" } + + side-channel-weakmap@1.0.2: + resolution: + { + integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + } + engines: { node: ">= 0.4" } + + side-channel@1.1.0: + resolution: + { + integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + } + engines: { node: ">= 0.4" } + signal-exit@3.0.7: resolution: { @@ -1411,6 +2463,19 @@ packages: } engines: { node: ">=20" } + source-map-support@0.5.21: + resolution: + { + integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + } + + source-map@0.6.1: + resolution: + { + integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + } + engines: { node: ">=0.10.0" } + sqlstring@2.3.3: resolution: { @@ -1418,12 +2483,26 @@ packages: } engines: { node: ">= 0.6" } + statuses@2.0.2: + resolution: + { + integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + } + engines: { node: ">= 0.8" } + std-env@3.10.0: resolution: { integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== } + streamsearch@1.1.0: + resolution: + { + integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + } + engines: { node: ">=10.0.0" } + string-argv@0.3.2: resolution: { @@ -1445,6 +2524,12 @@ packages: } engines: { node: ">=20" } + string_decoder@1.3.0: + resolution: + { + integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + } + strip-ansi@7.2.0: resolution: { @@ -1452,6 +2537,34 @@ packages: } engines: { node: ">=12" } + strip-bom@3.0.0: + resolution: + { + integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + } + engines: { node: ">=4" } + + strip-json-comments@2.0.1: + resolution: + { + integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + } + engines: { node: ">=0.10.0" } + + strtok3@10.3.5: + resolution: + { + integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA== + } + engines: { node: ">=18" } + + supports-preserve-symlinks-flag@1.0.0: + resolution: + { + integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + } + engines: { node: ">= 0.4" } + tinyexec@1.0.4: resolution: { @@ -1459,6 +2572,77 @@ packages: } engines: { node: ">=18" } + to-regex-range@5.0.1: + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + } + engines: { node: ">=8.0" } + + toidentifier@1.0.1: + resolution: + { + integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + } + engines: { node: ">=0.6" } + + token-types@6.1.2: + resolution: + { + integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww== + } + engines: { node: ">=14.16" } + + tree-kill@1.2.2: + resolution: + { + integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + } + hasBin: true + + ts-node-dev@2.0.0: + resolution: + { + integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w== + } + engines: { node: ">=0.8.0" } + hasBin: true + peerDependencies: + node-notifier: "*" + typescript: "*" + peerDependenciesMeta: + node-notifier: + optional: true + + ts-node@10.9.2: + resolution: + { + integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + } + hasBin: true + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + + tsconfig@7.0.0: + resolution: + { + integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== + } + + tslib@2.8.1: + resolution: + { + integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + } + turbo@2.9.3: resolution: { @@ -1473,18 +2657,79 @@ packages: } engines: { node: ">= 0.8.0" } + type-is@1.6.18: + resolution: + { + integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + } + engines: { node: ">= 0.6" } + + type-is@2.0.1: + resolution: + { + integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== + } + engines: { node: ">= 0.6" } + + typedarray@0.0.6: + resolution: + { + integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + } + + typescript@5.9.3: + resolution: + { + integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + } + engines: { node: ">=14.17" } + hasBin: true + + uid@2.0.2: + resolution: + { + integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g== + } + engines: { node: ">=8" } + + uint8array-extras@1.5.0: + resolution: + { + integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A== + } + engines: { node: ">=18" } + undici-types@7.18.2: resolution: { integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== } + unpipe@1.0.0: + resolution: + { + integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + } + engines: { node: ">= 0.8" } + uri-js@4.4.1: resolution: { integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== } + util-deprecate@1.0.2: + resolution: + { + integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + } + + v8-compile-cache-lib@3.0.1: + resolution: + { + integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + } + valibot@1.2.0: resolution: { @@ -1496,6 +2741,20 @@ packages: typescript: optional: true + validator@13.15.35: + resolution: + { + integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw== + } + engines: { node: ">= 0.10" } + + vary@1.1.2: + resolution: + { + integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + } + engines: { node: ">= 0.8" } + which@2.0.2: resolution: { @@ -1518,6 +2777,19 @@ packages: } engines: { node: ">=18" } + wrappy@1.0.2: + resolution: + { + integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + } + + xtend@4.0.2: + resolution: + { + integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + } + engines: { node: ">=0.4" } + yaml@2.8.3: resolution: { @@ -1526,6 +2798,13 @@ packages: engines: { node: ">= 14.6" } hasBin: true + yn@3.1.1: + resolution: + { + integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + } + engines: { node: ">=6" } + yocto-queue@0.1.0: resolution: { @@ -1540,6 +2819,8 @@ packages: } snapshots: + "@borewit/text-codec@0.2.2": {} + "@clack/core@0.5.0": dependencies: picocolors: 1.1.1 @@ -1551,6 +2832,10 @@ snapshots: picocolors: 1.1.1 sisteransi: 1.0.5 + "@cspotcode/source-map-support@0.8.1": + dependencies: + "@jridgewell/trace-mapping": 0.3.9 + "@electric-sql/pglite-socket@0.1.1(@electric-sql/pglite@0.4.1)": dependencies: "@electric-sql/pglite": 0.4.1 @@ -1610,15 +2895,86 @@ snapshots: "@humanwhocodes/retry@0.4.3": {} + "@jridgewell/resolve-uri@3.1.2": {} + + "@jridgewell/sourcemap-codec@1.5.5": {} + + "@jridgewell/trace-mapping@0.3.9": + dependencies: + "@jridgewell/resolve-uri": 3.1.2 + "@jridgewell/sourcemap-codec": 1.5.5 + "@kurkle/color@0.3.4": {} + "@lukeed/csprng@1.1.0": {} + + "@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)": + dependencies: + file-type: 21.3.4 + iterare: 1.2.1 + load-esm: 1.0.3 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.15.1 + transitivePeerDependencies: + - supports-color + + "@nestjs/config@4.0.3(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)": + dependencies: + "@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + dotenv: 17.2.3 + dotenv-expand: 12.0.3 + lodash: 4.17.23 + rxjs: 7.8.2 + + "@nestjs/core@11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2)": + dependencies: + "@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + "@nuxt/opencollective": 0.4.1 + fast-safe-stringify: 2.1.1 + iterare: 1.2.1 + path-to-regexp: 8.4.2 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + optionalDependencies: + "@nestjs/platform-express": 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18) + + "@nestjs/jwt@11.0.2(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))": + dependencies: + "@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + "@types/jsonwebtoken": 9.0.10 + jsonwebtoken: 9.0.3 + + "@nestjs/platform-express@11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)": + dependencies: + "@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + "@nestjs/core": 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cors: 2.8.6 + express: 5.2.1 + multer: 2.1.1 + path-to-regexp: 8.4.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + "@nuxt/opencollective@0.4.1": + dependencies: + consola: 3.4.2 + "@prisma/client-runtime-utils@7.6.0": {} - "@prisma/client@7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))": + "@prisma/client@7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)": dependencies: "@prisma/client-runtime-utils": 7.6.0 optionalDependencies: - prisma: 7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + prisma: 7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + typescript: 5.9.3 "@prisma/config@7.6.0": dependencies: @@ -1633,7 +2989,7 @@ snapshots: "@prisma/debug@7.6.0": {} - "@prisma/dev@0.24.3": + "@prisma/dev@0.24.3(typescript@5.9.3)": dependencies: "@electric-sql/pglite": 0.4.1 "@electric-sql/pglite-socket": 0.1.1(@electric-sql/pglite@0.4.1) @@ -1650,7 +3006,7 @@ snapshots: proper-lockfile: 4.1.2 remeda: 2.33.4 std-env: 3.10.0 - valibot: 1.2.0 + valibot: 1.2.0(typescript@5.9.3) zeptomatch: 2.1.0 transitivePeerDependencies: - typescript @@ -1753,6 +3109,23 @@ snapshots: "@standard-schema/spec@1.1.0": {} + "@tokenizer/inflate@0.4.1": + dependencies: + debug: 4.4.3 + token-types: 6.1.2 + transitivePeerDependencies: + - supports-color + + "@tokenizer/token@0.3.0": {} + + "@tsconfig/node10@1.0.12": {} + + "@tsconfig/node12@1.0.11": {} + + "@tsconfig/node14@1.0.3": {} + + "@tsconfig/node16@1.0.4": {} + "@turbo/darwin-64@2.9.3": optional: true @@ -1777,6 +3150,13 @@ snapshots: "@types/json-schema@7.0.15": {} + "@types/jsonwebtoken@9.0.10": + dependencies: + "@types/ms": 2.1.0 + "@types/node": 25.5.2 + + "@types/ms@2.1.0": {} + "@types/node@25.5.2": dependencies: undici-types: 7.18.2 @@ -1785,10 +3165,25 @@ snapshots: dependencies: csstype: 3.2.3 + "@types/strip-bom@3.0.0": {} + + "@types/strip-json-comments@0.0.30": {} + + "@types/validator@13.15.10": {} + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 + acorn-walk@8.3.5: + dependencies: + acorn: 8.16.0 + acorn@8.16.0: {} ajv@6.14.0: @@ -1813,18 +3208,64 @@ snapshots: ansi-styles@6.2.3: {} + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + append-field@1.0.0: {} + + arg@4.1.3: {} + aws-ssl-profiles@1.1.2: {} + balanced-match@1.0.2: {} + balanced-match@4.0.4: {} better-result@2.7.0: dependencies: "@clack/prompts": 0.11.0 + binary-extensions@2.3.0: {} + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + brace-expansion@1.1.13: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + buffer-equal-constant-time@1.0.1: {} + + buffer-from@1.1.2: {} + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + c12@3.1.0: dependencies: chokidar: 4.0.3 @@ -1840,10 +3281,32 @@ snapshots: pkg-types: 2.3.0 rc9: 2.1.2 + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + chart.js@4.5.1: dependencies: "@kurkle/color": 0.3.4 + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -1854,6 +3317,14 @@ snapshots: citty@0.2.2: {} + class-transformer@0.5.1: {} + + class-validator@0.15.1: + dependencies: + "@types/validator": 13.15.10 + libphonenumber-js: 1.12.41 + validator: 13.15.35 + cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -1867,10 +3338,34 @@ snapshots: commander@14.0.3: {} + concat-map@0.0.1: {} + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + confbox@0.2.4: {} consola@3.4.2: {} + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + create-require@1.1.1: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1891,10 +3386,36 @@ snapshots: denque@2.1.0: {} + depd@2.0.0: {} + destr@2.0.5: {} + diff@4.0.4: {} + + dotenv-expand@12.0.3: + dependencies: + dotenv: 16.6.1 + dotenv@16.6.1: {} + dotenv@17.2.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + dynamic-dedupe@0.3.0: + dependencies: + xtend: 4.0.2 + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + ee-first@1.1.1: {} + effect@3.20.0: dependencies: "@standard-schema/spec": 1.1.0 @@ -1904,10 +3425,22 @@ snapshots: empathic@2.0.0: {} + encodeurl@2.0.0: {} + env-paths@3.0.0: {} environment@1.1.0: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} eslint-scope@9.1.2: @@ -1976,8 +3509,43 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + eventemitter3@5.0.4: {} + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + exsolve@1.0.8: {} fast-check@3.23.2: @@ -1990,12 +3558,38 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-safe-stringify@2.1.1: {} + fast-uri@3.1.0: {} file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 + file-type@21.3.4: + dependencies: + "@tokenizer/inflate": 0.4.1 + strtok3: 10.3.5 + token-types: 6.1.2 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -2013,14 +3607,43 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + generate-function@2.3.1: dependencies: is-property: 1.0.2 get-east-asian-width@1.5.0: {} + get-intrinsic@1.3.0: + 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 + get-port-please@3.2.0: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + giget@2.0.0: dependencies: citty: 0.1.6 @@ -2030,20 +3653,49 @@ snapshots: nypm: 0.6.5 pathe: 2.0.3 + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + globals@17.4.0: {} + gopd@1.2.0: {} + graceful-fs@4.2.11: {} grammex@3.1.12: {} graphmatch@1.1.1: {} + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + hono@4.12.10: {} + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + http-status-codes@2.3.0: {} husky@9.1.7: {} @@ -2052,10 +3704,29 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} imurmurhash@0.1.4: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + is-extglob@2.1.1: {} is-fullwidth-code-point@5.1.0: @@ -2066,10 +3737,16 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-number@7.0.0: {} + + is-promise@4.0.0: {} + is-property@1.0.2: {} isexe@2.0.0: {} + iterare@1.2.1: {} + jiti@2.6.1: {} json-buffer@3.0.1: {} @@ -2080,6 +3757,30 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + jsonwebtoken@9.0.3: + 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.1.1 + ms: 2.1.3 + semver: 7.7.4 + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -2089,6 +3790,8 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + libphonenumber-js@1.12.41: {} + lint-staged@16.4.0: dependencies: commander: 14.0.3 @@ -2107,10 +3810,28 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.2 + load-esm@1.0.3: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 + 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.1.1: {} + + lodash@4.17.23: {} + log-update@6.1.0: dependencies: ansi-escapes: 7.3.0 @@ -2123,14 +3844,51 @@ snapshots: lru.min@1.1.4: {} + make-error@1.3.6: {} + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mimic-function@5.0.1: {} minimatch@10.2.5: dependencies: brace-expansion: 5.0.5 + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.13 + + minimist@1.2.8: {} + + mkdirp@1.0.4: {} + ms@2.1.3: {} + multer@2.1.1: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 2.0.0 + type-is: 1.6.18 + mysql2@3.15.3: dependencies: aws-ssl-profiles: 1.1.2 @@ -2149,16 +3907,32 @@ snapshots: natural-compare@1.4.0: {} + negotiator@1.0.0: {} + node-fetch-native@1.6.7: {} + normalize-path@3.0.0: {} + nypm@0.6.5: dependencies: citty: 0.2.2 pathe: 2.0.3 tinyexec: 1.0.4 + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + ohash@2.0.11: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -2180,16 +3954,26 @@ snapshots: dependencies: p-limit: 3.1.0 + parseurl@1.3.3: {} + path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} + path-parse@1.0.7: {} + + path-to-regexp@8.4.2: {} + pathe@2.0.3: {} perfect-debounce@1.0.0: {} picocolors@1.1.1: {} + picomatch@2.3.2: {} + picomatch@4.0.4: {} pkg-types@2.3.0: @@ -2204,14 +3988,16 @@ snapshots: prettier@3.8.1: {} - prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): dependencies: "@prisma/config": 7.6.0 - "@prisma/dev": 0.24.3 + "@prisma/dev": 0.24.3(typescript@5.9.3) "@prisma/engines": 7.6.0 "@prisma/studio-core": 0.27.3(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) mysql2: 3.15.3 postgres: 3.4.7 + optionalDependencies: + typescript: 5.9.3 transitivePeerDependencies: - "@types/react" - "@types/react-dom" @@ -2225,10 +4011,28 @@ snapshots: retry: 0.12.0 signal-exit: 3.0.7 + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + punycode@2.3.1: {} pure-rand@6.1.0: {} + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + rc9@2.1.2: dependencies: defu: 6.1.6 @@ -2241,12 +4045,30 @@ snapshots: react@19.2.4: {} + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + readdirp@4.1.2: {} + reflect-metadata@0.2.2: {} + remeda@2.33.4: {} require-from-string@2.0.2: {} + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -2256,18 +4078,95 @@ snapshots: rfdc@1.4.1: {} + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-buffer@5.2.1: {} + safer-buffer@2.1.2: {} scheduler@0.27.0: {} + semver@7.7.4: {} + + send@1.2.1: + 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 + transitivePeerDependencies: + - supports-color + seq-queue@0.0.5: {} + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -2284,10 +4183,21 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + sqlstring@2.3.3: {} + statuses@2.0.2: {} + std-env@3.10.0: {} + streamsearch@1.1.0: {} + string-argv@0.3.2: {} string-width@7.2.0: @@ -2301,12 +4211,85 @@ snapshots: get-east-asian-width: 1.5.0 strip-ansi: 7.2.0 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 + strip-bom@3.0.0: {} + + strip-json-comments@2.0.1: {} + + strtok3@10.3.5: + dependencies: + "@tokenizer/token": 0.3.0 + + supports-preserve-symlinks-flag@1.0.0: {} + tinyexec@1.0.4: {} + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + token-types@6.1.2: + dependencies: + "@borewit/text-codec": 0.2.2 + "@tokenizer/token": 0.3.0 + ieee754: 1.2.1 + + tree-kill@1.2.2: {} + + ts-node-dev@2.0.0(@types/node@25.5.2)(typescript@5.9.3): + dependencies: + chokidar: 3.6.0 + dynamic-dedupe: 0.3.0 + minimist: 1.2.8 + mkdirp: 1.0.4 + resolve: 1.22.11 + rimraf: 2.7.1 + source-map-support: 0.5.21 + tree-kill: 1.2.2 + ts-node: 10.9.2(@types/node@25.5.2)(typescript@5.9.3) + tsconfig: 7.0.0 + typescript: 5.9.3 + transitivePeerDependencies: + - "@swc/core" + - "@swc/wasm" + - "@types/node" + + ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3): + dependencies: + "@cspotcode/source-map-support": 0.8.1 + "@tsconfig/node10": 1.0.12 + "@tsconfig/node12": 1.0.11 + "@tsconfig/node14": 1.0.3 + "@tsconfig/node16": 1.0.4 + "@types/node": 25.5.2 + acorn: 8.16.0 + acorn-walk: 8.3.5 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.4 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tsconfig@7.0.0: + dependencies: + "@types/strip-bom": 3.0.0 + "@types/strip-json-comments": 0.0.30 + strip-bom: 3.0.0 + strip-json-comments: 2.0.1 + + tslib@2.8.1: {} + turbo@2.9.3: optionalDependencies: "@turbo/darwin-64": 2.9.3 @@ -2320,13 +4303,46 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typedarray@0.0.6: {} + + typescript@5.9.3: {} + + uid@2.0.2: + dependencies: + "@lukeed/csprng": 1.1.0 + + uint8array-extras@1.5.0: {} + undici-types@7.18.2: {} + unpipe@1.0.0: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - valibot@1.2.0: {} + util-deprecate@1.0.2: {} + + v8-compile-cache-lib@3.0.1: {} + + valibot@1.2.0(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + validator@13.15.35: {} + + vary@1.1.2: {} which@2.0.2: dependencies: @@ -2340,8 +4356,14 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.2.0 + wrappy@1.0.2: {} + + xtend@4.0.2: {} + yaml@2.8.3: {} + yn@3.1.1: {} + yocto-queue@0.1.0: {} zeptomatch@2.1.0: From 074942fab40dadb97da26e2217d4839741f2f7dc Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 21:12:14 +0800 Subject: [PATCH 18/80] feat(api-auth): implement refresh token rotation and revoke --- apps/api/.env.example | 1 + apps/api/src/auth/auth.controller.ts | 24 ++++- apps/api/src/auth/auth.service.ts | 107 ++++++++++++++++----- apps/api/src/auth/dto/refresh-token.dto.ts | 7 ++ 4 files changed, 111 insertions(+), 28 deletions(-) create mode 100644 apps/api/src/auth/dto/refresh-token.dto.ts diff --git a/apps/api/.env.example b/apps/api/.env.example index 6406c52..41b5593 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -1,4 +1,5 @@ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/todolist?schema=public" AUTH_ACCESS_SECRET="dev-access-secret" AUTH_ACCESS_EXPIRES_IN_SECONDS="900" +AUTH_REFRESH_EXPIRES_IN_SECONDS="2592000" AUTH_EMAIL_CODE_TTL_SECONDS="300" diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts index 6708a53..55d2329 100644 --- a/apps/api/src/auth/auth.controller.ts +++ b/apps/api/src/auth/auth.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, Post } from "@nestjs/common"; import { AuthService } from "./auth.service"; import { EmailLoginDto } from "./dto/email-login.dto"; +import { RefreshTokenDto } from "./dto/refresh-token.dto"; import { SendEmailCodeDto } from "./dto/send-email-code.dto"; @Controller("auth") @@ -15,14 +16,31 @@ export class AuthController { } @Post("email/login") - async loginWithEmailCode( - @Body() body: EmailLoginDto - ): Promise<{ + async loginWithEmailCode(@Body() body: EmailLoginDto): Promise<{ accessToken: string; tokenType: "Bearer"; expiresInSeconds: number; + refreshToken: string; + refreshExpiresInSeconds: number; user: { id: string; email: string }; }> { return this.authService.loginWithEmailCode(body.email, body.code); } + + @Post("token/refresh") + async refreshTokens(@Body() body: RefreshTokenDto): Promise<{ + accessToken: string; + tokenType: "Bearer"; + expiresInSeconds: number; + refreshToken: string; + refreshExpiresInSeconds: number; + user: { id: string; email: string }; + }> { + return this.authService.refreshTokens(body.refreshToken); + } + + @Post("token/revoke") + async revokeRefreshToken(@Body() body: RefreshTokenDto): Promise<{ success: boolean }> { + return this.authService.revokeRefreshToken(body.refreshToken); + } } diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index 9ede99c..d48f917 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -13,10 +13,27 @@ type AuthUser = { email: string; }; +type RefreshTokenEntry = { + userId: string; + expiresAt: number; + revokedAt?: number; +}; + +type AuthTokenResult = { + accessToken: string; + tokenType: "Bearer"; + expiresInSeconds: number; + refreshToken: string; + refreshExpiresInSeconds: number; + user: AuthUser; +}; + @Injectable() export class AuthService { private readonly emailCodeStore = new Map(); - private readonly userStore = new Map(); + private readonly userStoreByEmail = new Map(); + private readonly userStoreById = new Map(); + private readonly refreshTokenStore = new Map(); constructor( private readonly configService: ConfigService, @@ -39,15 +56,7 @@ export class AuthService { }; } - async loginWithEmailCode( - email: string, - code: string - ): Promise<{ - accessToken: string; - tokenType: "Bearer"; - expiresInSeconds: number; - user: AuthUser; - }> { + async loginWithEmailCode(email: string, code: string): Promise { const lowerEmail = email.toLowerCase(); const codeEntry = this.emailCodeStore.get(lowerEmail); @@ -67,24 +76,43 @@ export class AuthService { this.emailCodeStore.delete(lowerEmail); const user = this.getOrCreateUser(lowerEmail); - const expiresInSeconds = Number( - this.configService.get("AUTH_ACCESS_EXPIRES_IN_SECONDS") ?? 900 - ); - const accessToken = await this.jwtService.signAsync({ - sub: user.id, - email: user.email - }); + return this.issueTokens(user); + } - return { - accessToken, - tokenType: "Bearer", - expiresInSeconds, - user - }; + async refreshTokens(refreshToken: string): Promise { + const entry = this.refreshTokenStore.get(refreshToken); + if (!entry) { + throw new UnauthorizedException("刷新令牌不存在"); + } + if (entry.revokedAt) { + throw new UnauthorizedException("刷新令牌已注销"); + } + if (entry.expiresAt < Date.now()) { + this.refreshTokenStore.delete(refreshToken); + throw new UnauthorizedException("刷新令牌已过期"); + } + + const user = this.userStoreById.get(entry.userId); + if (!user) { + throw new UnauthorizedException("用户不存在"); + } + + entry.revokedAt = Date.now(); + return this.issueTokens(user); + } + + async revokeRefreshToken(refreshToken: string): Promise<{ success: boolean }> { + const entry = this.refreshTokenStore.get(refreshToken); + if (!entry) { + return { success: true }; + } + + entry.revokedAt = Date.now(); + return { success: true }; } private getOrCreateUser(email: string): AuthUser { - const existingUser = this.userStore.get(email); + const existingUser = this.userStoreByEmail.get(email); if (existingUser) { return existingUser; } @@ -93,7 +121,8 @@ export class AuthService { id: randomUUID(), email }; - this.userStore.set(email, newUser); + this.userStoreByEmail.set(email, newUser); + this.userStoreById.set(newUser.id, newUser); return newUser; } @@ -101,4 +130,32 @@ export class AuthService { private generateCode(): string { return String(Math.floor(100000 + Math.random() * 900000)); } + + private async issueTokens(user: AuthUser): Promise { + const accessExpiresInSeconds = Number( + this.configService.get("AUTH_ACCESS_EXPIRES_IN_SECONDS") ?? 900 + ); + const refreshExpiresInSeconds = Number( + this.configService.get("AUTH_REFRESH_EXPIRES_IN_SECONDS") ?? 2592000 + ); + const accessToken = await this.jwtService.signAsync({ + sub: user.id, + email: user.email + }); + const refreshToken = `${randomUUID()}${randomUUID()}`; + + this.refreshTokenStore.set(refreshToken, { + userId: user.id, + expiresAt: Date.now() + refreshExpiresInSeconds * 1000 + }); + + return { + accessToken, + tokenType: "Bearer", + expiresInSeconds: accessExpiresInSeconds, + refreshToken, + refreshExpiresInSeconds, + user + }; + } } diff --git a/apps/api/src/auth/dto/refresh-token.dto.ts b/apps/api/src/auth/dto/refresh-token.dto.ts new file mode 100644 index 0000000..2d91b3c --- /dev/null +++ b/apps/api/src/auth/dto/refresh-token.dto.ts @@ -0,0 +1,7 @@ +import { IsString, MinLength } from "class-validator"; + +export class RefreshTokenDto { + @IsString() + @MinLength(20) + refreshToken!: string; +} From 485fe43140013054b348d0858a74432c919ab6a6 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 21:15:32 +0800 Subject: [PATCH 19/80] feat(api-auth): add oauth strategies for github qq wechat --- apps/api/.env.example | 13 ++ apps/api/package.json | 2 + apps/api/src/auth/auth.controller.ts | 57 ++++++++- apps/api/src/auth/auth.module.ts | 7 +- .../src/auth/strategies/github.strategy.ts | 32 +++++ apps/api/src/auth/strategies/qq.strategy.ts | 33 +++++ .../src/auth/strategies/wechat.strategy.ts | 36 ++++++ pnpm-lock.yaml | 116 ++++++++++++++++++ 8 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 apps/api/src/auth/strategies/github.strategy.ts create mode 100644 apps/api/src/auth/strategies/qq.strategy.ts create mode 100644 apps/api/src/auth/strategies/wechat.strategy.ts diff --git a/apps/api/.env.example b/apps/api/.env.example index 41b5593..69712b2 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -3,3 +3,16 @@ AUTH_ACCESS_SECRET="dev-access-secret" AUTH_ACCESS_EXPIRES_IN_SECONDS="900" AUTH_REFRESH_EXPIRES_IN_SECONDS="2592000" AUTH_EMAIL_CODE_TTL_SECONDS="300" +OAUTH_GITHUB_CLIENT_ID="github-client-id" +OAUTH_GITHUB_CLIENT_SECRET="github-client-secret" +OAUTH_GITHUB_CALLBACK_URL="http://localhost:3000/auth/oauth/github/callback" +OAUTH_QQ_CLIENT_ID="qq-client-id" +OAUTH_QQ_CLIENT_SECRET="qq-client-secret" +OAUTH_QQ_CALLBACK_URL="http://localhost:3000/auth/oauth/qq/callback" +OAUTH_QQ_AUTH_URL="https://graph.qq.com/oauth2.0/authorize" +OAUTH_QQ_TOKEN_URL="https://graph.qq.com/oauth2.0/token" +OAUTH_WECHAT_CLIENT_ID="wechat-client-id" +OAUTH_WECHAT_CLIENT_SECRET="wechat-client-secret" +OAUTH_WECHAT_CALLBACK_URL="http://localhost:3000/auth/oauth/wechat/callback" +OAUTH_WECHAT_AUTH_URL="https://open.weixin.qq.com/connect/qrconnect" +OAUTH_WECHAT_TOKEN_URL="https://api.weixin.qq.com/sns/oauth2/access_token" diff --git a/apps/api/package.json b/apps/api/package.json index e02e42d..a136df6 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -15,6 +15,8 @@ "license": "GPL-3.0-or-later", "devDependencies": { "@types/node": "^25.5.2", + "@types/passport-github2": "^1.2.9", + "@types/passport-oauth2": "^1.8.0", "dotenv": "^16.6.1", "prisma": "^7.6.0", "ts-node": "^10.9.2", diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts index 55d2329..ecf7bd3 100644 --- a/apps/api/src/auth/auth.controller.ts +++ b/apps/api/src/auth/auth.controller.ts @@ -1,4 +1,5 @@ -import { Body, Controller, Post } from "@nestjs/common"; +import { Body, Controller, Get, Post, Req, UseGuards } from "@nestjs/common"; +import { AuthGuard } from "@nestjs/passport"; import { AuthService } from "./auth.service"; import { EmailLoginDto } from "./dto/email-login.dto"; import { RefreshTokenDto } from "./dto/refresh-token.dto"; @@ -43,4 +44,58 @@ export class AuthController { async revokeRefreshToken(@Body() body: RefreshTokenDto): Promise<{ success: boolean }> { return this.authService.revokeRefreshToken(body.refreshToken); } + + @Get("oauth/github") + @UseGuards(AuthGuard("github")) + githubLogin(): void {} + + @Get("oauth/github/callback") + @UseGuards(AuthGuard("github")) + githubCallback(@Req() req: { user: unknown }): { + success: boolean; + provider: "github"; + profile: unknown; + } { + return { + success: true, + provider: "github", + profile: req.user + }; + } + + @Get("oauth/qq") + @UseGuards(AuthGuard("qq")) + qqLogin(): void {} + + @Get("oauth/qq/callback") + @UseGuards(AuthGuard("qq")) + qqCallback(@Req() req: { user: unknown }): { + success: boolean; + provider: "qq"; + profile: unknown; + } { + return { + success: true, + provider: "qq", + profile: req.user + }; + } + + @Get("oauth/wechat") + @UseGuards(AuthGuard("wechat")) + wechatLogin(): void {} + + @Get("oauth/wechat/callback") + @UseGuards(AuthGuard("wechat")) + wechatCallback(@Req() req: { user: unknown }): { + success: boolean; + provider: "wechat"; + profile: unknown; + } { + return { + success: true, + provider: "wechat", + profile: req.user + }; + } } diff --git a/apps/api/src/auth/auth.module.ts b/apps/api/src/auth/auth.module.ts index f8b73ce..e108ac3 100644 --- a/apps/api/src/auth/auth.module.ts +++ b/apps/api/src/auth/auth.module.ts @@ -1,12 +1,17 @@ import { Module } from "@nestjs/common"; import { ConfigModule, ConfigService } from "@nestjs/config"; import { JwtModule } from "@nestjs/jwt"; +import { PassportModule } from "@nestjs/passport"; import { AuthController } from "./auth.controller"; import { AuthService } from "./auth.service"; +import { GithubStrategy } from "./strategies/github.strategy"; +import { QqStrategy } from "./strategies/qq.strategy"; +import { WechatStrategy } from "./strategies/wechat.strategy"; @Module({ imports: [ ConfigModule, + PassportModule.register({ session: false }), JwtModule.registerAsync({ inject: [ConfigService], useFactory: (configService: ConfigService) => { @@ -22,6 +27,6 @@ import { AuthService } from "./auth.service"; }) ], controllers: [AuthController], - providers: [AuthService] + providers: [AuthService, GithubStrategy, QqStrategy, WechatStrategy] }) export class AuthModule {} diff --git a/apps/api/src/auth/strategies/github.strategy.ts b/apps/api/src/auth/strategies/github.strategy.ts new file mode 100644 index 0000000..3b3133d --- /dev/null +++ b/apps/api/src/auth/strategies/github.strategy.ts @@ -0,0 +1,32 @@ +import { Injectable } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { PassportStrategy } from "@nestjs/passport"; +import { Profile, Strategy } from "passport-github2"; + +@Injectable() +export class GithubStrategy extends PassportStrategy(Strategy, "github") { + constructor(configService: ConfigService) { + super({ + clientID: configService.get("OAUTH_GITHUB_CLIENT_ID") ?? "github-client-id", + clientSecret: + configService.get("OAUTH_GITHUB_CLIENT_SECRET") ?? "github-client-secret", + callbackURL: + configService.get("OAUTH_GITHUB_CALLBACK_URL") ?? + "http://localhost:3000/auth/oauth/github/callback", + scope: ["user:email"] + }); + } + + async validate( + accessToken: string, + refreshToken: string, + profile: Profile + ): Promise<{ provider: "github"; accessToken: string; refreshToken: string; profile: Profile }> { + return { + provider: "github", + accessToken, + refreshToken, + profile + }; + } +} diff --git a/apps/api/src/auth/strategies/qq.strategy.ts b/apps/api/src/auth/strategies/qq.strategy.ts new file mode 100644 index 0000000..5191151 --- /dev/null +++ b/apps/api/src/auth/strategies/qq.strategy.ts @@ -0,0 +1,33 @@ +import { Injectable } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { PassportStrategy } from "@nestjs/passport"; +import { Strategy } from "passport-oauth2"; + +@Injectable() +export class QqStrategy extends PassportStrategy(Strategy, "qq") { + constructor(configService: ConfigService) { + super({ + authorizationURL: + configService.get("OAUTH_QQ_AUTH_URL") ?? "https://graph.qq.com/oauth2.0/authorize", + tokenURL: + configService.get("OAUTH_QQ_TOKEN_URL") ?? "https://graph.qq.com/oauth2.0/token", + clientID: configService.get("OAUTH_QQ_CLIENT_ID") ?? "qq-client-id", + clientSecret: configService.get("OAUTH_QQ_CLIENT_SECRET") ?? "qq-client-secret", + callbackURL: + configService.get("OAUTH_QQ_CALLBACK_URL") ?? + "http://localhost:3000/auth/oauth/qq/callback", + scope: ["get_user_info"] + }); + } + + async validate( + accessToken: string, + refreshToken: string + ): Promise<{ provider: "qq"; accessToken: string; refreshToken: string }> { + return { + provider: "qq", + accessToken, + refreshToken + }; + } +} diff --git a/apps/api/src/auth/strategies/wechat.strategy.ts b/apps/api/src/auth/strategies/wechat.strategy.ts new file mode 100644 index 0000000..1e4343b --- /dev/null +++ b/apps/api/src/auth/strategies/wechat.strategy.ts @@ -0,0 +1,36 @@ +import { Injectable } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { PassportStrategy } from "@nestjs/passport"; +import { Strategy } from "passport-oauth2"; + +@Injectable() +export class WechatStrategy extends PassportStrategy(Strategy, "wechat") { + constructor(configService: ConfigService) { + super({ + authorizationURL: + configService.get("OAUTH_WECHAT_AUTH_URL") ?? + "https://open.weixin.qq.com/connect/qrconnect", + tokenURL: + configService.get("OAUTH_WECHAT_TOKEN_URL") ?? + "https://api.weixin.qq.com/sns/oauth2/access_token", + clientID: configService.get("OAUTH_WECHAT_CLIENT_ID") ?? "wechat-client-id", + clientSecret: + configService.get("OAUTH_WECHAT_CLIENT_SECRET") ?? "wechat-client-secret", + callbackURL: + configService.get("OAUTH_WECHAT_CALLBACK_URL") ?? + "http://localhost:3000/auth/oauth/wechat/callback", + scope: ["snsapi_login"] + }); + } + + async validate( + accessToken: string, + refreshToken: string + ): Promise<{ provider: "wechat"; accessToken: string; refreshToken: string }> { + return { + provider: "wechat", + accessToken, + refreshToken + }; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c8607a..a4d24c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,6 +43,9 @@ importers: "@nestjs/jwt": specifier: ^11.0.2 version: 11.0.2(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)) + "@nestjs/passport": + specifier: ^11.0.5 + version: 11.0.5(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0) "@nestjs/platform-express": specifier: ^11.1.18 version: 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18) @@ -55,6 +58,15 @@ importers: class-validator: specifier: ^0.15.1 version: 0.15.1 + passport: + specifier: ^0.7.0 + version: 0.7.0 + passport-github2: + specifier: ^0.1.12 + version: 0.1.12 + passport-oauth2: + specifier: ^1.8.0 + version: 1.8.0 reflect-metadata: specifier: ^0.2.2 version: 0.2.2 @@ -320,6 +332,15 @@ packages: peerDependencies: "@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + "@nestjs/passport@11.0.5": + resolution: + { + integrity: sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ== + } + peerDependencies: + "@nestjs/common": ^10.0.0 || ^11.0.0 + passport: ^0.5.0 || ^0.6.0 || ^0.7.0 + "@nestjs/platform-express@11.1.18": resolution: { @@ -787,6 +808,13 @@ packages: } engines: { node: 18 || 20 || >=22 } + base64url@3.0.1: + resolution: + { + integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + } + engines: { node: ">=6.0.0" } + better-result@2.7.0: resolution: { @@ -2002,6 +2030,12 @@ packages: engines: { node: ">=18" } hasBin: true + oauth@0.10.2: + resolution: + { + integrity: sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q== + } + object-assign@4.1.1: resolution: { @@ -2070,6 +2104,34 @@ packages: } engines: { node: ">= 0.8" } + passport-github2@0.1.12: + resolution: + { + integrity: sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw== + } + engines: { node: ">= 0.8.0" } + + passport-oauth2@1.8.0: + resolution: + { + integrity: sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA== + } + engines: { node: ">= 0.4.0" } + + passport-strategy@1.0.0: + resolution: + { + integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== + } + engines: { node: ">= 0.4.0" } + + passport@0.7.0: + resolution: + { + integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ== + } + engines: { node: ">= 0.4.0" } + path-exists@4.0.0: resolution: { @@ -2109,6 +2171,12 @@ packages: integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== } + pause@0.0.1: + resolution: + { + integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== + } + perfect-debounce@1.0.0: resolution: { @@ -2685,6 +2753,12 @@ packages: engines: { node: ">=14.17" } hasBin: true + uid2@0.0.4: + resolution: + { + integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA== + } + uid@2.0.2: resolution: { @@ -2724,6 +2798,13 @@ packages: integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== } + utils-merge@1.0.1: + resolution: + { + integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + } + engines: { node: ">= 0.4.0" } + v8-compile-cache-lib@3.0.1: resolution: { @@ -2951,6 +3032,11 @@ snapshots: "@types/jsonwebtoken": 9.0.10 jsonwebtoken: 9.0.3 + "@nestjs/passport@11.0.5(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)": + dependencies: + "@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + passport: 0.7.0 + "@nestjs/platform-express@11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)": dependencies: "@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -3223,6 +3309,8 @@ snapshots: balanced-match@4.0.4: {} + base64url@3.0.1: {} + better-result@2.7.0: dependencies: "@clack/prompts": 0.11.0 @@ -3919,6 +4007,8 @@ snapshots: pathe: 2.0.3 tinyexec: 1.0.4 + oauth@0.10.2: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -3956,6 +4046,26 @@ snapshots: parseurl@1.3.3: {} + passport-github2@0.1.12: + dependencies: + passport-oauth2: 1.8.0 + + passport-oauth2@1.8.0: + dependencies: + base64url: 3.0.1 + oauth: 0.10.2 + passport-strategy: 1.0.0 + uid2: 0.0.4 + utils-merge: 1.0.1 + + passport-strategy@1.0.0: {} + + passport@0.7.0: + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -3968,6 +4078,8 @@ snapshots: pathe@2.0.3: {} + pause@0.0.1: {} + perfect-debounce@1.0.0: {} picocolors@1.1.1: {} @@ -4318,6 +4430,8 @@ snapshots: typescript@5.9.3: {} + uid2@0.0.4: {} + uid@2.0.2: dependencies: "@lukeed/csprng": 1.1.0 @@ -4334,6 +4448,8 @@ snapshots: util-deprecate@1.0.2: {} + utils-merge@1.0.1: {} + v8-compile-cache-lib@3.0.1: {} valibot@1.2.0(typescript@5.9.3): From 62b0514da787761be8e43b653f5877dd65509237 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 4 Apr 2026 21:21:43 +0800 Subject: [PATCH 20/80] feat(api-auth): add totp 2fa enrollment and verify --- apps/api/.env.example | 1 + apps/api/package.json | 2 + apps/api/src/auth/auth.controller.ts | 19 + apps/api/src/auth/auth.service.ts | 50 +++ .../api/src/auth/dto/two-factor-enroll.dto.ts | 6 + .../api/src/auth/dto/two-factor-verify.dto.ts | 11 + pnpm-lock.yaml | 411 +++++++++++++----- 7 files changed, 384 insertions(+), 116 deletions(-) create mode 100644 apps/api/src/auth/dto/two-factor-enroll.dto.ts create mode 100644 apps/api/src/auth/dto/two-factor-verify.dto.ts diff --git a/apps/api/.env.example b/apps/api/.env.example index 69712b2..4394718 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -3,6 +3,7 @@ AUTH_ACCESS_SECRET="dev-access-secret" AUTH_ACCESS_EXPIRES_IN_SECONDS="900" AUTH_REFRESH_EXPIRES_IN_SECONDS="2592000" AUTH_EMAIL_CODE_TTL_SECONDS="300" +AUTH_TOTP_ISSUER="TodoList" OAUTH_GITHUB_CLIENT_ID="github-client-id" OAUTH_GITHUB_CLIENT_SECRET="github-client-secret" OAUTH_GITHUB_CALLBACK_URL="http://localhost:3000/auth/oauth/github/callback" diff --git a/apps/api/package.json b/apps/api/package.json index a136df6..a1327aa 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -30,9 +30,11 @@ "@nestjs/core": "^11.1.18", "@nestjs/jwt": "^11.0.2", "@nestjs/platform-express": "^11.1.18", + "@otplib/preset-default": "^12.0.1", "@prisma/client": "^7.6.0", "class-transformer": "^0.5.1", "class-validator": "^0.15.1", + "otplib": "^13.4.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2" } diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts index ecf7bd3..1641223 100644 --- a/apps/api/src/auth/auth.controller.ts +++ b/apps/api/src/auth/auth.controller.ts @@ -4,6 +4,8 @@ import { AuthService } from "./auth.service"; import { EmailLoginDto } from "./dto/email-login.dto"; import { RefreshTokenDto } from "./dto/refresh-token.dto"; import { SendEmailCodeDto } from "./dto/send-email-code.dto"; +import { TwoFactorEnrollDto } from "./dto/two-factor-enroll.dto"; +import { TwoFactorVerifyDto } from "./dto/two-factor-verify.dto"; @Controller("auth") export class AuthController { @@ -45,6 +47,23 @@ export class AuthController { return this.authService.revokeRefreshToken(body.refreshToken); } + @Post("2fa/enroll") + async enrollTwoFactor(@Body() body: TwoFactorEnrollDto): Promise<{ + userId: string; + secret: string; + otpauthUrl: string; + enabled: boolean; + }> { + return this.authService.enrollTwoFactor(body.email); + } + + @Post("2fa/verify") + async verifyTwoFactor( + @Body() body: TwoFactorVerifyDto + ): Promise<{ success: boolean; enabled: boolean }> { + return this.authService.verifyTwoFactor(body.email, body.token); + } + @Get("oauth/github") @UseGuards(AuthGuard("github")) githubLogin(): void {} diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index d48f917..4e25fa1 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -2,6 +2,7 @@ import { Injectable, UnauthorizedException } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { JwtService } from "@nestjs/jwt"; import { randomUUID } from "node:crypto"; +import { authenticator } from "@otplib/preset-default"; type EmailCodeEntry = { code: string; @@ -19,6 +20,11 @@ type RefreshTokenEntry = { revokedAt?: number; }; +type TwoFactorEntry = { + secret: string; + enabled: boolean; +}; + type AuthTokenResult = { accessToken: string; tokenType: "Bearer"; @@ -34,6 +40,7 @@ export class AuthService { private readonly userStoreByEmail = new Map(); private readonly userStoreById = new Map(); private readonly refreshTokenStore = new Map(); + private readonly twoFactorStore = new Map(); constructor( private readonly configService: ConfigService, @@ -111,6 +118,49 @@ export class AuthService { return { success: true }; } + async enrollTwoFactor( + email: string + ): Promise<{ userId: string; secret: string; otpauthUrl: string; enabled: boolean }> { + const user = this.getOrCreateUser(email.toLowerCase()); + const secret = authenticator.generateSecret(); + const issuer = this.configService.get("AUTH_TOTP_ISSUER") ?? "TodoList"; + const otpauthUrl = authenticator.keyuri(user.email, issuer, secret); + + this.twoFactorStore.set(user.id, { + secret, + enabled: false + }); + + return { + userId: user.id, + secret, + otpauthUrl, + enabled: false + }; + } + + async verifyTwoFactor( + email: string, + token: string + ): Promise<{ success: boolean; enabled: boolean }> { + const user = this.getOrCreateUser(email.toLowerCase()); + const entry = this.twoFactorStore.get(user.id); + if (!entry) { + throw new UnauthorizedException("尚未启用两步验证"); + } + + const valid = authenticator.check(token, entry.secret); + if (!valid) { + throw new UnauthorizedException("两步验证码错误"); + } + + entry.enabled = true; + return { + success: true, + enabled: true + }; + } + private getOrCreateUser(email: string): AuthUser { const existingUser = this.userStoreByEmail.get(email); if (existingUser) { diff --git a/apps/api/src/auth/dto/two-factor-enroll.dto.ts b/apps/api/src/auth/dto/two-factor-enroll.dto.ts new file mode 100644 index 0000000..2dea4ef --- /dev/null +++ b/apps/api/src/auth/dto/two-factor-enroll.dto.ts @@ -0,0 +1,6 @@ +import { IsEmail } from "class-validator"; + +export class TwoFactorEnrollDto { + @IsEmail() + email!: string; +} diff --git a/apps/api/src/auth/dto/two-factor-verify.dto.ts b/apps/api/src/auth/dto/two-factor-verify.dto.ts new file mode 100644 index 0000000..f9317bd --- /dev/null +++ b/apps/api/src/auth/dto/two-factor-verify.dto.ts @@ -0,0 +1,11 @@ +import { IsEmail, IsString, Length, Matches } from "class-validator"; + +export class TwoFactorVerifyDto { + @IsEmail() + email!: string; + + @IsString() + @Length(6, 6) + @Matches(/^\d{6}$/) + token!: string; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4d24c9..051c827 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,12 +43,12 @@ importers: "@nestjs/jwt": specifier: ^11.0.2 version: 11.0.2(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)) - "@nestjs/passport": - specifier: ^11.0.5 - version: 11.0.5(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0) "@nestjs/platform-express": specifier: ^11.1.18 version: 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18) + "@otplib/preset-default": + specifier: ^12.0.1 + version: 12.0.1 "@prisma/client": specifier: ^7.6.0 version: 7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) @@ -58,15 +58,9 @@ importers: class-validator: specifier: ^0.15.1 version: 0.15.1 - passport: - specifier: ^0.7.0 - version: 0.7.0 - passport-github2: - specifier: ^0.1.12 - version: 0.1.12 - passport-oauth2: - specifier: ^1.8.0 - version: 1.8.0 + otplib: + specifier: ^13.4.0 + version: 13.4.0 reflect-metadata: specifier: ^0.2.2 version: 0.2.2 @@ -77,6 +71,12 @@ importers: "@types/node": specifier: ^25.5.2 version: 25.5.2 + "@types/passport-github2": + specifier: ^1.2.9 + version: 1.2.9 + "@types/passport-oauth2": + specifier: ^1.8.0 + version: 1.8.0 dotenv: specifier: ^16.6.1 version: 16.6.1 @@ -332,15 +332,6 @@ packages: peerDependencies: "@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 - "@nestjs/passport@11.0.5": - resolution: - { - integrity: sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ== - } - peerDependencies: - "@nestjs/common": ^10.0.0 || ^11.0.0 - passport: ^0.5.0 || ^0.6.0 || ^0.7.0 - "@nestjs/platform-express@11.1.18": resolution: { @@ -350,6 +341,13 @@ packages: "@nestjs/common": ^11.0.0 "@nestjs/core": ^11.0.0 + "@noble/hashes@2.0.1": + resolution: + { + integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw== + } + engines: { node: ">= 20.19.0" } + "@nuxt/opencollective@0.4.1": resolution: { @@ -358,6 +356,69 @@ packages: engines: { node: ^14.18.0 || >=16.10.0, npm: ">=5.10.0" } hasBin: true + "@otplib/core@12.0.1": + resolution: + { + integrity: sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA== + } + + "@otplib/core@13.4.0": + resolution: + { + integrity: sha512-JqOGcvZQi2wIkEQo8f3/iAjstavpXy6gouIDMHygjNuH6Q0FjbHOiXMdcE94RwfgDNMABhzwUmvaPsxvgm9NYw== + } + + "@otplib/hotp@13.4.0": + resolution: + { + integrity: sha512-MJjE0x06mn2ptymz5qZmQveb+vWFuaIftqE0b5/TZZqUOK7l97cV8lRTmid5BpAQMwJDNLW6RnYxGeCRiNdekw== + } + + "@otplib/plugin-base32-scure@13.4.0": + resolution: + { + integrity: sha512-/t9YWJmMbB8bF5z8mXrBZc2FXBe8B/3hG5FhWr9K8cFwFhyxScbPysmZe8s1UTzSA6N+s8Uv8aIfCtVXPNjJWw== + } + + "@otplib/plugin-crypto-noble@13.4.0": + resolution: + { + integrity: sha512-KrvE4m7Zv+TT1944HzgqFJWJpKb6AyoxDbvhPStmBqdMlv5Gekb80d66cuFRL08kkPgJ5gXUSb5SFpYeB+bACg== + } + + "@otplib/plugin-crypto@12.0.1": + resolution: + { + integrity: sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g== + } + deprecated: Please upgrade to v13 of otplib. Refer to otplib docs for migration paths + + "@otplib/plugin-thirty-two@12.0.1": + resolution: + { + integrity: sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA== + } + deprecated: Please upgrade to v13 of otplib. Refer to otplib docs for migration paths + + "@otplib/preset-default@12.0.1": + resolution: + { + integrity: sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ== + } + deprecated: Please upgrade to v13 of otplib. Refer to otplib docs for migration paths + + "@otplib/totp@13.4.0": + resolution: + { + integrity: sha512-dK+vl0f0ekzf6mCENRI9AKS2NJUC7OjI3+X8e7QSnhQ2WM7I+i4PGpb3QxKi5hxjTtwVuoZwXR2CFtXdcRtNdQ== + } + + "@otplib/uri@13.4.0": + resolution: + { + integrity: sha512-x1ozBa5bPbdZCrrTL/HK21qchiK7jYElTu+0ft22abeEhiLYgH1+SIULvOcVk3CK8YwF4kdcidvkq4ciejucJA== + } + "@prisma/client-runtime-utils@7.6.0": resolution: { @@ -555,6 +616,12 @@ packages: "@types/react": optional: true + "@scure/base@2.0.0": + resolution: + { + integrity: sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w== + } + "@standard-schema/spec@1.1.0": resolution: { @@ -646,6 +713,18 @@ packages: cpu: [arm64] os: [win32] + "@types/body-parser@1.19.6": + resolution: + { + integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== + } + + "@types/connect@3.4.38": + resolution: + { + integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + } + "@types/esrecurse@4.3.1": resolution: { @@ -658,6 +737,24 @@ packages: integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== } + "@types/express-serve-static-core@5.1.1": + resolution: + { + integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A== + } + + "@types/express@5.0.6": + resolution: + { + integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== + } + + "@types/http-errors@2.0.5": + resolution: + { + integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== + } + "@types/json-schema@7.0.15": resolution: { @@ -682,12 +779,60 @@ packages: integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg== } + "@types/oauth@0.9.6": + resolution: + { + integrity: sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA== + } + + "@types/passport-github2@1.2.9": + resolution: + { + integrity: sha512-/nMfiPK2E6GKttwBzwj0Wjaot8eHrM57hnWxu52o6becr5/kXlH/4yE2v2rh234WGvSgEEzIII02Nc5oC5xEHA== + } + + "@types/passport-oauth2@1.8.0": + resolution: + { + integrity: sha512-6//z+4orIOy/g3zx17HyQ71GSRK4bs7Sb+zFasRoc2xzlv7ZCJ+vkDBYFci8U6HY+or6Zy7ajf4mz4rK7nsWJQ== + } + + "@types/passport@1.0.17": + resolution: + { + integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg== + } + + "@types/qs@6.15.0": + resolution: + { + integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow== + } + + "@types/range-parser@1.2.7": + resolution: + { + integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + } + "@types/react@19.2.14": resolution: { integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== } + "@types/send@1.2.1": + resolution: + { + integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== + } + + "@types/serve-static@2.2.0": + resolution: + { + integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== + } + "@types/strip-bom@3.0.0": resolution: { @@ -808,13 +953,6 @@ packages: } engines: { node: 18 || 20 || >=22 } - base64url@3.0.1: - resolution: - { - integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== - } - engines: { node: ">=6.0.0" } - better-result@2.7.0: resolution: { @@ -2030,12 +2168,6 @@ packages: engines: { node: ">=18" } hasBin: true - oauth@0.10.2: - resolution: - { - integrity: sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q== - } - object-assign@4.1.1: resolution: { @@ -2083,6 +2215,12 @@ packages: } engines: { node: ">= 0.8.0" } + otplib@13.4.0: + resolution: + { + integrity: sha512-RUcYcRMCgRWhUE/XabRppXpUwCwaWBNHe5iPXhdvP8wwDGpGpsIf/kxX/ec3zFsOaM1Oq8lEhUqDwk6W7DHkwg== + } + p-limit@3.1.0: resolution: { @@ -2104,34 +2242,6 @@ packages: } engines: { node: ">= 0.8" } - passport-github2@0.1.12: - resolution: - { - integrity: sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw== - } - engines: { node: ">= 0.8.0" } - - passport-oauth2@1.8.0: - resolution: - { - integrity: sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA== - } - engines: { node: ">= 0.4.0" } - - passport-strategy@1.0.0: - resolution: - { - integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== - } - engines: { node: ">= 0.4.0" } - - passport@0.7.0: - resolution: - { - integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ== - } - engines: { node: ">= 0.4.0" } - path-exists@4.0.0: resolution: { @@ -2171,12 +2281,6 @@ packages: integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== } - pause@0.0.1: - resolution: - { - integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== - } - perfect-debounce@1.0.0: resolution: { @@ -2633,6 +2737,13 @@ packages: } engines: { node: ">= 0.4" } + thirty-two@1.0.2: + resolution: + { + integrity: sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA== + } + engines: { node: ">=0.2.6" } + tinyexec@1.0.4: resolution: { @@ -2753,12 +2864,6 @@ packages: engines: { node: ">=14.17" } hasBin: true - uid2@0.0.4: - resolution: - { - integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA== - } - uid@2.0.2: resolution: { @@ -2798,13 +2903,6 @@ packages: integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== } - utils-merge@1.0.1: - resolution: - { - integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - } - engines: { node: ">= 0.4.0" } - v8-compile-cache-lib@3.0.1: resolution: { @@ -3032,11 +3130,6 @@ snapshots: "@types/jsonwebtoken": 9.0.10 jsonwebtoken: 9.0.3 - "@nestjs/passport@11.0.5(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)": - dependencies: - "@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - passport: 0.7.0 - "@nestjs/platform-express@11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)": dependencies: "@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -3049,10 +3142,56 @@ snapshots: transitivePeerDependencies: - supports-color + "@noble/hashes@2.0.1": {} + "@nuxt/opencollective@0.4.1": dependencies: consola: 3.4.2 + "@otplib/core@12.0.1": {} + + "@otplib/core@13.4.0": {} + + "@otplib/hotp@13.4.0": + dependencies: + "@otplib/core": 13.4.0 + "@otplib/uri": 13.4.0 + + "@otplib/plugin-base32-scure@13.4.0": + dependencies: + "@otplib/core": 13.4.0 + "@scure/base": 2.0.0 + + "@otplib/plugin-crypto-noble@13.4.0": + dependencies: + "@noble/hashes": 2.0.1 + "@otplib/core": 13.4.0 + + "@otplib/plugin-crypto@12.0.1": + dependencies: + "@otplib/core": 12.0.1 + + "@otplib/plugin-thirty-two@12.0.1": + dependencies: + "@otplib/core": 12.0.1 + thirty-two: 1.0.2 + + "@otplib/preset-default@12.0.1": + dependencies: + "@otplib/core": 12.0.1 + "@otplib/plugin-crypto": 12.0.1 + "@otplib/plugin-thirty-two": 12.0.1 + + "@otplib/totp@13.4.0": + dependencies: + "@otplib/core": 13.4.0 + "@otplib/hotp": 13.4.0 + "@otplib/uri": 13.4.0 + + "@otplib/uri@13.4.0": + dependencies: + "@otplib/core": 13.4.0 + "@prisma/client-runtime-utils@7.6.0": {} "@prisma/client@7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)": @@ -3193,6 +3332,8 @@ snapshots: optionalDependencies: "@types/react": 19.2.14 + "@scure/base@2.0.0": {} + "@standard-schema/spec@1.1.0": {} "@tokenizer/inflate@0.4.1": @@ -3230,10 +3371,34 @@ snapshots: "@turbo/windows-arm64@2.9.3": optional: true + "@types/body-parser@1.19.6": + dependencies: + "@types/connect": 3.4.38 + "@types/node": 25.5.2 + + "@types/connect@3.4.38": + dependencies: + "@types/node": 25.5.2 + "@types/esrecurse@4.3.1": {} "@types/estree@1.0.8": {} + "@types/express-serve-static-core@5.1.1": + dependencies: + "@types/node": 25.5.2 + "@types/qs": 6.15.0 + "@types/range-parser": 1.2.7 + "@types/send": 1.2.1 + + "@types/express@5.0.6": + dependencies: + "@types/body-parser": 1.19.6 + "@types/express-serve-static-core": 5.1.1 + "@types/serve-static": 2.2.0 + + "@types/http-errors@2.0.5": {} + "@types/json-schema@7.0.15": {} "@types/jsonwebtoken@9.0.10": @@ -3247,10 +3412,43 @@ snapshots: dependencies: undici-types: 7.18.2 + "@types/oauth@0.9.6": + dependencies: + "@types/node": 25.5.2 + + "@types/passport-github2@1.2.9": + dependencies: + "@types/express": 5.0.6 + "@types/passport": 1.0.17 + "@types/passport-oauth2": 1.8.0 + + "@types/passport-oauth2@1.8.0": + dependencies: + "@types/express": 5.0.6 + "@types/oauth": 0.9.6 + "@types/passport": 1.0.17 + + "@types/passport@1.0.17": + dependencies: + "@types/express": 5.0.6 + + "@types/qs@6.15.0": {} + + "@types/range-parser@1.2.7": {} + "@types/react@19.2.14": dependencies: csstype: 3.2.3 + "@types/send@1.2.1": + dependencies: + "@types/node": 25.5.2 + + "@types/serve-static@2.2.0": + dependencies: + "@types/http-errors": 2.0.5 + "@types/node": 25.5.2 + "@types/strip-bom@3.0.0": {} "@types/strip-json-comments@0.0.30": {} @@ -3309,8 +3507,6 @@ snapshots: balanced-match@4.0.4: {} - base64url@3.0.1: {} - better-result@2.7.0: dependencies: "@clack/prompts": 0.11.0 @@ -4007,8 +4203,6 @@ snapshots: pathe: 2.0.3 tinyexec: 1.0.4 - oauth@0.10.2: {} - object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -4036,6 +4230,15 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + otplib@13.4.0: + dependencies: + "@otplib/core": 13.4.0 + "@otplib/hotp": 13.4.0 + "@otplib/plugin-base32-scure": 13.4.0 + "@otplib/plugin-crypto-noble": 13.4.0 + "@otplib/totp": 13.4.0 + "@otplib/uri": 13.4.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -4046,26 +4249,6 @@ snapshots: parseurl@1.3.3: {} - passport-github2@0.1.12: - dependencies: - passport-oauth2: 1.8.0 - - passport-oauth2@1.8.0: - dependencies: - base64url: 3.0.1 - oauth: 0.10.2 - passport-strategy: 1.0.0 - uid2: 0.0.4 - utils-merge: 1.0.1 - - passport-strategy@1.0.0: {} - - passport@0.7.0: - dependencies: - passport-strategy: 1.0.0 - pause: 0.0.1 - utils-merge: 1.0.1 - path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -4078,8 +4261,6 @@ snapshots: pathe@2.0.3: {} - pause@0.0.1: {} - perfect-debounce@1.0.0: {} picocolors@1.1.1: {} @@ -4341,6 +4522,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + thirty-two@1.0.2: {} + tinyexec@1.0.4: {} to-regex-range@5.0.1: @@ -4430,8 +4613,6 @@ snapshots: typescript@5.9.3: {} - uid2@0.0.4: {} - uid@2.0.2: dependencies: "@lukeed/csprng": 1.1.0 @@ -4448,8 +4629,6 @@ snapshots: util-deprecate@1.0.2: {} - utils-merge@1.0.1: {} - v8-compile-cache-lib@3.0.1: {} valibot@1.2.0(typescript@5.9.3): From 8f6ff38a32ad97358d0dacc996d92fb8fea71a15 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 00:01:28 +0800 Subject: [PATCH 21/80] feat(api-task): implement task crud with tag filters --- apps/api/src/app.module.ts | 6 +- apps/api/src/prisma/prisma.module.ts | 9 + apps/api/src/prisma/prisma.service.ts | 13 + apps/api/src/task/dto/create-task.dto.ts | 64 +++ apps/api/src/task/dto/list-tasks-query.dto.ts | 92 +++++ apps/api/src/task/dto/update-task.dto.ts | 65 +++ apps/api/src/task/task.controller.ts | 71 ++++ apps/api/src/task/task.module.ts | 11 + apps/api/src/task/task.service.ts | 390 ++++++++++++++++++ apps/api/tsconfig.json | 4 +- 10 files changed, 722 insertions(+), 3 deletions(-) create mode 100644 apps/api/src/prisma/prisma.module.ts create mode 100644 apps/api/src/prisma/prisma.service.ts create mode 100644 apps/api/src/task/dto/create-task.dto.ts create mode 100644 apps/api/src/task/dto/list-tasks-query.dto.ts create mode 100644 apps/api/src/task/dto/update-task.dto.ts create mode 100644 apps/api/src/task/task.controller.ts create mode 100644 apps/api/src/task/task.module.ts create mode 100644 apps/api/src/task/task.service.ts diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 8cd6181..bbf571a 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -1,6 +1,8 @@ import { Module } from "@nestjs/common"; import { ConfigModule } from "@nestjs/config"; import { AuthModule } from "./auth/auth.module"; +import { PrismaModule } from "./prisma/prisma.module"; +import { TaskModule } from "./task/task.module"; @Module({ imports: [ @@ -8,7 +10,9 @@ import { AuthModule } from "./auth/auth.module"; isGlobal: true, envFilePath: ".env" }), - AuthModule + PrismaModule, + AuthModule, + TaskModule ] }) export class AppModule {} diff --git a/apps/api/src/prisma/prisma.module.ts b/apps/api/src/prisma/prisma.module.ts new file mode 100644 index 0000000..7a94e73 --- /dev/null +++ b/apps/api/src/prisma/prisma.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from "@nestjs/common"; +import { PrismaService } from "./prisma.service"; + +@Global() +@Module({ + providers: [PrismaService], + exports: [PrismaService] +}) +export class PrismaModule {} diff --git a/apps/api/src/prisma/prisma.service.ts b/apps/api/src/prisma/prisma.service.ts new file mode 100644 index 0000000..72e40f6 --- /dev/null +++ b/apps/api/src/prisma/prisma.service.ts @@ -0,0 +1,13 @@ +import { Injectable, OnModuleDestroy, OnModuleInit } from "@nestjs/common"; +import { PrismaClient } from "../../generated/prisma/client"; + +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { + async onModuleInit(): Promise { + await this.$connect(); + } + + async onModuleDestroy(): Promise { + await this.$disconnect(); + } +} diff --git a/apps/api/src/task/dto/create-task.dto.ts b/apps/api/src/task/dto/create-task.dto.ts new file mode 100644 index 0000000..69c2000 --- /dev/null +++ b/apps/api/src/task/dto/create-task.dto.ts @@ -0,0 +1,64 @@ +import { Transform } from "class-transformer"; +import { + IsArray, + IsDateString, + IsEnum, + IsObject, + IsOptional, + IsString, + MaxLength, + MinLength +} from "class-validator"; +import { TaskPriority, TaskStatus } from "../../../generated/prisma/client"; + +function normalizeString(value: unknown): unknown { + if (typeof value !== "string") { + return value; + } + + return value.trim(); +} + +export class CreateTaskDto { + @Transform(({ value }) => normalizeString(value)) + @IsString() + @MinLength(1) + @MaxLength(120) + title!: string; + + @IsOptional() + @IsObject() + contentJson?: Record; + + @Transform(({ value }) => normalizeString(value)) + @IsOptional() + @IsString() + @MaxLength(20000) + contentText?: string; + + @IsOptional() + @IsEnum(TaskPriority) + priority?: TaskPriority; + + @IsOptional() + @IsEnum(TaskStatus) + status?: TaskStatus; + + @IsOptional() + @IsDateString() + ddl?: string; + + @Transform(({ value }) => { + if (!Array.isArray(value)) { + return value; + } + + return value.map((item) => normalizeString(item)); + }) + @IsOptional() + @IsArray() + @IsString({ each: true }) + @MinLength(1, { each: true }) + @MaxLength(30, { each: true }) + tagNames?: string[]; +} diff --git a/apps/api/src/task/dto/list-tasks-query.dto.ts b/apps/api/src/task/dto/list-tasks-query.dto.ts new file mode 100644 index 0000000..baa5afb --- /dev/null +++ b/apps/api/src/task/dto/list-tasks-query.dto.ts @@ -0,0 +1,92 @@ +import { Transform, Type } from "class-transformer"; +import { IsArray, IsEnum, IsInt, IsOptional, IsString, Max, MaxLength, Min } from "class-validator"; +import { TaskPriority, TaskStatus } from "../../../generated/prisma/client"; + +export enum TaskSortBy { + CREATED_AT = "createdAt", + UPDATED_AT = "updatedAt", + DDL = "ddl" +} + +export enum TaskSortOrder { + ASC = "asc", + DESC = "desc" +} + +function normalizeString(value: unknown): string | undefined { + if (typeof value !== "string") { + return undefined; + } + + const normalized = value.trim(); + if (!normalized) { + return undefined; + } + + return normalized; +} + +export class ListTasksQueryDto { + @IsOptional() + @IsEnum(TaskStatus) + status?: TaskStatus; + + @IsOptional() + @IsEnum(TaskPriority) + priority?: TaskPriority; + + @Transform(({ value }) => { + if (value === undefined || value === null || value === "") { + return undefined; + } + + if (Array.isArray(value)) { + const normalized = value + .map((item) => normalizeString(item)) + .filter((item): item is string => item !== undefined); + return normalized.length > 0 ? normalized : undefined; + } + + if (typeof value === "string") { + const normalized = value + .split(",") + .map((item) => normalizeString(item)) + .filter((item): item is string => item !== undefined); + return normalized.length > 0 ? normalized : undefined; + } + + return undefined; + }) + @IsOptional() + @IsArray() + @IsString({ each: true }) + @MaxLength(30, { each: true }) + tags?: string[]; + + @Transform(({ value }) => normalizeString(value)) + @IsOptional() + @IsString() + @MaxLength(120) + keyword?: string; + + @Type(() => Number) + @IsOptional() + @IsInt() + @Min(1) + page?: number; + + @Type(() => Number) + @IsOptional() + @IsInt() + @Min(1) + @Max(100) + pageSize?: number; + + @IsOptional() + @IsEnum(TaskSortBy) + sortBy?: TaskSortBy; + + @IsOptional() + @IsEnum(TaskSortOrder) + sortOrder?: TaskSortOrder; +} diff --git a/apps/api/src/task/dto/update-task.dto.ts b/apps/api/src/task/dto/update-task.dto.ts new file mode 100644 index 0000000..23b8894 --- /dev/null +++ b/apps/api/src/task/dto/update-task.dto.ts @@ -0,0 +1,65 @@ +import { Transform } from "class-transformer"; +import { + IsArray, + IsDateString, + IsEnum, + IsObject, + IsOptional, + IsString, + MaxLength, + MinLength +} from "class-validator"; +import { TaskPriority, TaskStatus } from "../../../generated/prisma/client"; + +function normalizeString(value: unknown): unknown { + if (typeof value !== "string") { + return value; + } + + return value.trim(); +} + +export class UpdateTaskDto { + @Transform(({ value }) => normalizeString(value)) + @IsOptional() + @IsString() + @MinLength(1) + @MaxLength(120) + title?: string; + + @IsOptional() + @IsObject() + contentJson?: Record; + + @Transform(({ value }) => normalizeString(value)) + @IsOptional() + @IsString() + @MaxLength(20000) + contentText?: string; + + @IsOptional() + @IsEnum(TaskPriority) + priority?: TaskPriority; + + @IsOptional() + @IsEnum(TaskStatus) + status?: TaskStatus; + + @IsOptional() + @IsDateString() + ddl?: string; + + @Transform(({ value }) => { + if (!Array.isArray(value)) { + return value; + } + + return value.map((item) => normalizeString(item)); + }) + @IsOptional() + @IsArray() + @IsString({ each: true }) + @MinLength(1, { each: true }) + @MaxLength(30, { each: true }) + tagNames?: string[]; +} diff --git a/apps/api/src/task/task.controller.ts b/apps/api/src/task/task.controller.ts new file mode 100644 index 0000000..33b9710 --- /dev/null +++ b/apps/api/src/task/task.controller.ts @@ -0,0 +1,71 @@ +import { + Body, + Controller, + Delete, + Get, + Headers, + Param, + Patch, + Post, + Query, + UnauthorizedException +} from "@nestjs/common"; +import { CreateTaskDto } from "./dto/create-task.dto"; +import { ListTasksQueryDto } from "./dto/list-tasks-query.dto"; +import { UpdateTaskDto } from "./dto/update-task.dto"; +import { ListTasksResponse, TaskResponse, TaskService } from "./task.service"; + +@Controller("tasks") +export class TaskController { + constructor(private readonly taskService: TaskService) {} + + @Get() + async listTasks( + @Headers("x-user-id") userIdHeader: string | string[] | undefined, + @Query() query: ListTasksQueryDto + ): Promise { + return this.taskService.listTasks(this.resolveUserId(userIdHeader), query); + } + + @Get(":taskId") + async getTaskById( + @Headers("x-user-id") userIdHeader: string | string[] | undefined, + @Param("taskId") taskId: string + ): Promise { + return this.taskService.getTaskById(this.resolveUserId(userIdHeader), taskId); + } + + @Post() + async createTask( + @Headers("x-user-id") userIdHeader: string | string[] | undefined, + @Body() body: CreateTaskDto + ): Promise { + return this.taskService.createTask(this.resolveUserId(userIdHeader), body); + } + + @Patch(":taskId") + async updateTask( + @Headers("x-user-id") userIdHeader: string | string[] | undefined, + @Param("taskId") taskId: string, + @Body() body: UpdateTaskDto + ): Promise { + return this.taskService.updateTask(this.resolveUserId(userIdHeader), taskId, body); + } + + @Delete(":taskId") + async deleteTask( + @Headers("x-user-id") userIdHeader: string | string[] | undefined, + @Param("taskId") taskId: string + ): Promise<{ success: boolean }> { + return this.taskService.deleteTask(this.resolveUserId(userIdHeader), taskId); + } + + private resolveUserId(userIdHeader: string | string[] | undefined): string { + const userId = Array.isArray(userIdHeader) ? userIdHeader[0] : userIdHeader; + if (!userId) { + throw new UnauthorizedException("缺少用户上下文"); + } + + return userId; + } +} diff --git a/apps/api/src/task/task.module.ts b/apps/api/src/task/task.module.ts new file mode 100644 index 0000000..8226093 --- /dev/null +++ b/apps/api/src/task/task.module.ts @@ -0,0 +1,11 @@ +import { Module } from "@nestjs/common"; +import { PrismaModule } from "../prisma/prisma.module"; +import { TaskController } from "./task.controller"; +import { TaskService } from "./task.service"; + +@Module({ + imports: [PrismaModule], + controllers: [TaskController], + providers: [TaskService] +}) +export class TaskModule {} diff --git a/apps/api/src/task/task.service.ts b/apps/api/src/task/task.service.ts new file mode 100644 index 0000000..3f62dea --- /dev/null +++ b/apps/api/src/task/task.service.ts @@ -0,0 +1,390 @@ +import { Injectable, NotFoundException } from "@nestjs/common"; +import { Prisma, TaskPriority, TaskStatus } from "../../generated/prisma/client"; +import { PrismaService } from "../prisma/prisma.service"; +import { CreateTaskDto } from "./dto/create-task.dto"; +import { ListTasksQueryDto, TaskSortBy, TaskSortOrder } from "./dto/list-tasks-query.dto"; +import { UpdateTaskDto } from "./dto/update-task.dto"; + +type TaskEntity = Prisma.TaskGetPayload<{ + include: { + taskTags: { + include: { + tag: { + select: { + name: true; + }; + }; + }; + }; + }; +}>; + +export type TaskResponse = { + id: string; + title: string; + contentJson: unknown | null; + contentText: string | null; + priority: TaskPriority; + status: TaskStatus; + ddl: string | null; + completedAt: string | null; + version: number; + tags: string[]; + createdAt: string; + updatedAt: string; +}; + +export type ListTasksResponse = { + items: TaskResponse[]; + page: number; + pageSize: number; + total: number; +}; + +@Injectable() +export class TaskService { + constructor(private readonly prismaService: PrismaService) {} + + async listTasks(userId: string, query: ListTasksQueryDto): Promise { + const page = query.page ?? 1; + const pageSize = query.pageSize ?? 20; + const skip = (page - 1) * pageSize; + + const where = this.buildWhereInput(userId, query); + const orderBy = this.buildOrderByInput(query); + + const [items, total] = await Promise.all([ + this.prismaService.task.findMany({ + where, + orderBy, + skip, + take: pageSize, + include: { + taskTags: { + include: { + tag: { + select: { + name: true + } + } + } + } + } + }), + this.prismaService.task.count({ where }) + ]); + + return { + items: items.map((item) => this.serializeTask(item)), + page, + pageSize, + total + }; + } + + async getTaskById(userId: string, taskId: string): Promise { + const task = await this.prismaService.task.findFirst({ + where: { + id: taskId, + userId + }, + include: { + taskTags: { + include: { + tag: { + select: { + name: true + } + } + } + } + } + }); + + if (!task) { + throw new NotFoundException("任务不存在"); + } + + return this.serializeTask(task); + } + + async createTask(userId: string, body: CreateTaskDto): Promise { + const tagNames = this.normalizeTagNames(body.tagNames); + const nextStatus = body.status ?? TaskStatus.TODO; + const contentJson = + body.contentJson !== undefined ? (body.contentJson as Prisma.InputJsonValue) : undefined; + + const task = await this.prismaService.$transaction(async (tx) => { + const createdTask = await tx.task.create({ + data: { + userId, + title: body.title, + contentJson, + contentText: body.contentText ?? null, + priority: body.priority ?? TaskPriority.MEDIUM, + status: nextStatus, + ddl: body.ddl ? new Date(body.ddl) : null, + completedAt: nextStatus === TaskStatus.DONE ? new Date() : null + } + }); + + await this.replaceTaskTags(tx, userId, createdTask.id, tagNames); + + return tx.task.findUniqueOrThrow({ + where: { id: createdTask.id }, + include: { + taskTags: { + include: { + tag: { + select: { + name: true + } + } + } + } + } + }); + }); + + return this.serializeTask(task); + } + + async updateTask(userId: string, taskId: string, body: UpdateTaskDto): Promise { + const currentTask = await this.prismaService.task.findFirst({ + where: { + id: taskId, + userId + }, + select: { + id: true, + status: true + } + }); + + if (!currentTask) { + throw new NotFoundException("任务不存在"); + } + + const data: Prisma.TaskUpdateInput = { + version: { + increment: 1 + } + }; + + if (body.title !== undefined) { + data.title = body.title; + } + if (body.contentJson !== undefined) { + data.contentJson = body.contentJson as Prisma.InputJsonValue; + } + if (body.contentText !== undefined) { + data.contentText = body.contentText; + } + if (body.priority !== undefined) { + data.priority = body.priority; + } + if (body.status !== undefined) { + data.status = body.status; + if (body.status === TaskStatus.DONE && currentTask.status !== TaskStatus.DONE) { + data.completedAt = new Date(); + } else if (body.status !== TaskStatus.DONE) { + data.completedAt = null; + } + } + if (body.ddl !== undefined) { + data.ddl = body.ddl ? new Date(body.ddl) : null; + } + + const shouldReplaceTags = body.tagNames !== undefined; + const nextTagNames = this.normalizeTagNames(body.tagNames); + + const task = await this.prismaService.$transaction(async (tx) => { + await tx.task.update({ + where: { id: taskId }, + data + }); + + if (shouldReplaceTags) { + await this.replaceTaskTags(tx, userId, taskId, nextTagNames); + } + + return tx.task.findUniqueOrThrow({ + where: { id: taskId }, + include: { + taskTags: { + include: { + tag: { + select: { + name: true + } + } + } + } + } + }); + }); + + return this.serializeTask(task); + } + + async deleteTask(userId: string, taskId: string): Promise<{ success: boolean }> { + const deleted = await this.prismaService.task.deleteMany({ + where: { + id: taskId, + userId + } + }); + + if (deleted.count === 0) { + throw new NotFoundException("任务不存在"); + } + + return { success: true }; + } + + private buildWhereInput(userId: string, query: ListTasksQueryDto): Prisma.TaskWhereInput { + const where: Prisma.TaskWhereInput = { + userId + }; + + if (query.status !== undefined) { + where.status = query.status; + } + + if (query.priority !== undefined) { + where.priority = query.priority; + } + + if (query.tags !== undefined && query.tags.length > 0) { + where.taskTags = { + some: { + tag: { + name: { + in: query.tags + } + } + } + }; + } + + if (query.keyword !== undefined && query.keyword.length > 0) { + where.OR = [ + { + title: { + contains: query.keyword, + mode: "insensitive" + } + }, + { + contentText: { + contains: query.keyword, + mode: "insensitive" + } + } + ]; + } + + return where; + } + + private buildOrderByInput(query: ListTasksQueryDto): Prisma.TaskOrderByWithRelationInput { + const order: Prisma.SortOrder = + query.sortOrder === TaskSortOrder.ASC ? Prisma.SortOrder.asc : Prisma.SortOrder.desc; + + if (query.sortBy === TaskSortBy.CREATED_AT) { + return { createdAt: order }; + } + + if (query.sortBy === TaskSortBy.DDL) { + return { ddl: order }; + } + + return { updatedAt: order }; + } + + private normalizeTagNames(tagNames: string[] | undefined): string[] { + if (!tagNames) { + return []; + } + + const result: string[] = []; + const uniqueNames = new Set(); + + for (const rawTagName of tagNames) { + const normalized = rawTagName.trim(); + if (!normalized) { + continue; + } + + const uniqueKey = normalized.toLocaleLowerCase(); + if (uniqueNames.has(uniqueKey)) { + continue; + } + + uniqueNames.add(uniqueKey); + result.push(normalized); + } + + return result; + } + + private async replaceTaskTags( + tx: Prisma.TransactionClient, + userId: string, + taskId: string, + tagNames: string[] + ): Promise { + await tx.taskTag.deleteMany({ + where: { + taskId + } + }); + + if (tagNames.length === 0) { + return; + } + + const tags = await Promise.all( + tagNames.map((name) => + tx.tag.upsert({ + where: { + userId_name: { + userId, + name + } + }, + update: {}, + create: { + userId, + name + } + }) + ) + ); + + await tx.taskTag.createMany({ + data: tags.map((tag) => ({ + taskId, + tagId: tag.id + })), + skipDuplicates: true + }); + } + + private serializeTask(task: TaskEntity): TaskResponse { + return { + id: task.id, + title: task.title, + contentJson: task.contentJson, + contentText: task.contentText, + priority: task.priority, + status: task.status, + ddl: task.ddl?.toISOString() ?? null, + completedAt: task.completedAt?.toISOString() ?? null, + version: task.version, + tags: task.taskTags.map((taskTag) => taskTag.tag.name), + createdAt: task.createdAt.toISOString(), + updatedAt: task.updatedAt.toISOString() + }; + } +} diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index a6aded2..17d29bc 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -2,9 +2,9 @@ "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../packages/tsconfig/nest-app.json", "compilerOptions": { - "rootDir": "src", + "rootDir": ".", "outDir": "dist" }, - "include": ["src/**/*.ts"], + "include": ["src/**/*.ts", "generated/prisma/**/*.ts"], "exclude": ["dist", "node_modules"] } From bd3241504f1fb3d012d239809e8b1213b6dae530 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 00:05:39 +0800 Subject: [PATCH 22/80] feat(api-attachment): add minio presigned upload flow --- apps/api/.env.example | 8 + apps/api/package.json | 2 + apps/api/src/app.module.ts | 4 +- .../src/attachment/attachment.controller.ts | 38 + apps/api/src/attachment/attachment.module.ts | 11 + apps/api/src/attachment/attachment.service.ts | 207 +++ .../attachment/dto/complete-attachment.dto.ts | 89 + .../attachment/dto/presign-attachment.dto.ts | 35 + pnpm-lock.yaml | 1512 +++++++++++++++++ 9 files changed, 1905 insertions(+), 1 deletion(-) create mode 100644 apps/api/src/attachment/attachment.controller.ts create mode 100644 apps/api/src/attachment/attachment.module.ts create mode 100644 apps/api/src/attachment/attachment.service.ts create mode 100644 apps/api/src/attachment/dto/complete-attachment.dto.ts create mode 100644 apps/api/src/attachment/dto/presign-attachment.dto.ts diff --git a/apps/api/.env.example b/apps/api/.env.example index 4394718..68af79c 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -17,3 +17,11 @@ OAUTH_WECHAT_CLIENT_SECRET="wechat-client-secret" OAUTH_WECHAT_CALLBACK_URL="http://localhost:3000/auth/oauth/wechat/callback" OAUTH_WECHAT_AUTH_URL="https://open.weixin.qq.com/connect/qrconnect" OAUTH_WECHAT_TOKEN_URL="https://api.weixin.qq.com/sns/oauth2/access_token" +S3_ENDPOINT="http://127.0.0.1:9000" +S3_REGION="us-east-1" +S3_BUCKET="todolist" +S3_ACCESS_KEY_ID="minioadmin" +S3_SECRET_ACCESS_KEY="minioadmin" +S3_FORCE_PATH_STYLE="true" +S3_PRESIGN_EXPIRES_SECONDS="900" +S3_PUBLIC_BASE_URL="http://127.0.0.1:9000" diff --git a/apps/api/package.json b/apps/api/package.json index a1327aa..5cda8d9 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -25,6 +25,8 @@ }, "private": true, "dependencies": { + "@aws-sdk/client-s3": "^3.1024.0", + "@aws-sdk/s3-request-presigner": "^3.1024.0", "@nestjs/common": "^11.1.18", "@nestjs/config": "^4.0.3", "@nestjs/core": "^11.1.18", diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index bbf571a..4f6ee3f 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -1,5 +1,6 @@ import { Module } from "@nestjs/common"; import { ConfigModule } from "@nestjs/config"; +import { AttachmentModule } from "./attachment/attachment.module"; import { AuthModule } from "./auth/auth.module"; import { PrismaModule } from "./prisma/prisma.module"; import { TaskModule } from "./task/task.module"; @@ -12,7 +13,8 @@ import { TaskModule } from "./task/task.module"; }), PrismaModule, AuthModule, - TaskModule + TaskModule, + AttachmentModule ] }) export class AppModule {} diff --git a/apps/api/src/attachment/attachment.controller.ts b/apps/api/src/attachment/attachment.controller.ts new file mode 100644 index 0000000..6baec6e --- /dev/null +++ b/apps/api/src/attachment/attachment.controller.ts @@ -0,0 +1,38 @@ +import { Body, Controller, Headers, Post, UnauthorizedException } from "@nestjs/common"; +import { + AttachmentResponse, + AttachmentService, + PresignAttachmentResponse +} from "./attachment.service"; +import { CompleteAttachmentDto } from "./dto/complete-attachment.dto"; +import { PresignAttachmentDto } from "./dto/presign-attachment.dto"; + +@Controller("attachments") +export class AttachmentController { + constructor(private readonly attachmentService: AttachmentService) {} + + @Post("presign") + async presignAttachment( + @Headers("x-user-id") userIdHeader: string | string[] | undefined, + @Body() body: PresignAttachmentDto + ): Promise { + return this.attachmentService.presignAttachment(this.resolveUserId(userIdHeader), body); + } + + @Post("complete") + async completeAttachment( + @Headers("x-user-id") userIdHeader: string | string[] | undefined, + @Body() body: CompleteAttachmentDto + ): Promise { + return this.attachmentService.completeAttachment(this.resolveUserId(userIdHeader), body); + } + + private resolveUserId(userIdHeader: string | string[] | undefined): string { + const userId = Array.isArray(userIdHeader) ? userIdHeader[0] : userIdHeader; + if (!userId) { + throw new UnauthorizedException("缺少用户上下文"); + } + + return userId; + } +} diff --git a/apps/api/src/attachment/attachment.module.ts b/apps/api/src/attachment/attachment.module.ts new file mode 100644 index 0000000..cd8dfe3 --- /dev/null +++ b/apps/api/src/attachment/attachment.module.ts @@ -0,0 +1,11 @@ +import { Module } from "@nestjs/common"; +import { PrismaModule } from "../prisma/prisma.module"; +import { AttachmentController } from "./attachment.controller"; +import { AttachmentService } from "./attachment.service"; + +@Module({ + imports: [PrismaModule], + controllers: [AttachmentController], + providers: [AttachmentService] +}) +export class AttachmentModule {} diff --git a/apps/api/src/attachment/attachment.service.ts b/apps/api/src/attachment/attachment.service.ts new file mode 100644 index 0000000..7ddd5b5 --- /dev/null +++ b/apps/api/src/attachment/attachment.service.ts @@ -0,0 +1,207 @@ +import { randomUUID } from "node:crypto"; +import { Injectable, NotFoundException } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; +import { AttachmentType } from "../../generated/prisma/client"; +import { PrismaService } from "../prisma/prisma.service"; +import { CompleteAttachmentDto } from "./dto/complete-attachment.dto"; +import { PresignAttachmentDto } from "./dto/presign-attachment.dto"; + +export type PresignAttachmentResponse = { + method: "PUT"; + uploadUrl: string; + bucket: string; + objectKey: string; + objectUrl: string; + expiresInSeconds: number; + headers: { + "Content-Type": string; + }; +}; + +export type AttachmentResponse = { + id: string; + taskId: string | null; + type: AttachmentType; + url: string; + mimeType: string | null; + fileName: string | null; + fileSize: number; + width: number | null; + height: number | null; + durationMs: number | null; + checksum: string | null; + createdAt: string; + updatedAt: string; +}; + +@Injectable() +export class AttachmentService { + private s3Client: S3Client | null = null; + + constructor( + private readonly configService: ConfigService, + private readonly prismaService: PrismaService + ) {} + + async presignAttachment( + userId: string, + body: PresignAttachmentDto + ): Promise { + if (body.taskId) { + await this.ensureTaskOwnership(userId, body.taskId); + } + + const bucket = this.getDefaultBucket(); + const objectKey = this.generateObjectKey(userId, body.fileName); + const objectUrl = this.resolveObjectUrl(bucket, objectKey); + const expiresInSeconds = this.getPresignExpiresInSeconds(); + + const command = new PutObjectCommand({ + Bucket: bucket, + Key: objectKey, + ContentType: body.mimeType, + ContentLength: body.fileSize + }); + + const uploadUrl = await getSignedUrl(this.getS3Client(), command, { + expiresIn: expiresInSeconds + }); + + return { + method: "PUT", + uploadUrl, + bucket, + objectKey, + objectUrl, + expiresInSeconds, + headers: { + "Content-Type": body.mimeType + } + }; + } + + async completeAttachment( + userId: string, + body: CompleteAttachmentDto + ): Promise { + if (body.taskId) { + await this.ensureTaskOwnership(userId, body.taskId); + } + + const bucket = body.bucket ?? this.getDefaultBucket(); + const objectUrl = this.resolveObjectUrl(bucket, body.objectKey); + const attachment = await this.prismaService.attachment.create({ + data: { + userId, + taskId: body.taskId ?? null, + type: body.type ?? this.resolveAttachmentType(body.mimeType), + url: objectUrl, + mimeType: body.mimeType, + fileName: body.fileName, + fileSize: body.fileSize, + width: body.width ?? null, + height: body.height ?? null, + durationMs: body.durationMs ?? null, + checksum: body.checksum ?? null + } + }); + + return { + id: attachment.id, + taskId: attachment.taskId, + type: attachment.type, + url: attachment.url, + mimeType: attachment.mimeType, + fileName: attachment.fileName, + fileSize: attachment.fileSize, + width: attachment.width, + height: attachment.height, + durationMs: attachment.durationMs, + checksum: attachment.checksum, + createdAt: attachment.createdAt.toISOString(), + updatedAt: attachment.updatedAt.toISOString() + }; + } + + private getS3Client(): S3Client { + if (this.s3Client) { + return this.s3Client; + } + + const endpoint = this.configService.get("S3_ENDPOINT") ?? "http://127.0.0.1:9000"; + const region = this.configService.get("S3_REGION") ?? "us-east-1"; + const forcePathStyle = + this.configService.get("S3_FORCE_PATH_STYLE")?.toLowerCase() !== "false"; + + this.s3Client = new S3Client({ + endpoint, + region, + forcePathStyle, + credentials: { + accessKeyId: this.configService.get("S3_ACCESS_KEY_ID") ?? "minioadmin", + secretAccessKey: this.configService.get("S3_SECRET_ACCESS_KEY") ?? "minioadmin" + } + }); + + return this.s3Client; + } + + private getDefaultBucket(): string { + return this.configService.get("S3_BUCKET") ?? "todolist"; + } + + private getPresignExpiresInSeconds(): number { + const configValue = Number(this.configService.get("S3_PRESIGN_EXPIRES_SECONDS") ?? 900); + if (!Number.isFinite(configValue) || configValue <= 0) { + return 900; + } + + return Math.min(configValue, 604800); + } + + private generateObjectKey(userId: string, fileName: string): string { + const safeFileName = fileName.replace(/[^\w.-]+/g, "_"); + const datePrefix = new Date().toISOString().slice(0, 10); + return `${userId}/${datePrefix}/${randomUUID()}-${safeFileName}`; + } + + private resolveObjectUrl(bucket: string, objectKey: string): string { + const publicBaseUrl = this.configService.get("S3_PUBLIC_BASE_URL"); + if (publicBaseUrl) { + return `${publicBaseUrl.replace(/\/+$/, "")}/${bucket}/${objectKey}`; + } + + const endpoint = this.configService.get("S3_ENDPOINT") ?? "http://127.0.0.1:9000"; + return `${endpoint.replace(/\/+$/, "")}/${bucket}/${objectKey}`; + } + + private resolveAttachmentType(mimeType: string): AttachmentType { + if (mimeType.startsWith("image/")) { + return AttachmentType.IMAGE; + } + + if (mimeType.startsWith("video/")) { + return AttachmentType.VIDEO; + } + + return AttachmentType.FILE; + } + + private async ensureTaskOwnership(userId: string, taskId: string): Promise { + const task = await this.prismaService.task.findFirst({ + where: { + id: taskId, + userId + }, + select: { + id: true + } + }); + + if (!task) { + throw new NotFoundException("任务不存在"); + } + } +} diff --git a/apps/api/src/attachment/dto/complete-attachment.dto.ts b/apps/api/src/attachment/dto/complete-attachment.dto.ts new file mode 100644 index 0000000..2318e3b --- /dev/null +++ b/apps/api/src/attachment/dto/complete-attachment.dto.ts @@ -0,0 +1,89 @@ +import { Transform, Type } from "class-transformer"; +import { + IsEnum, + IsInt, + IsOptional, + IsString, + Max, + MaxLength, + Min, + MinLength +} from "class-validator"; +import { AttachmentType } from "../../../generated/prisma/client"; + +function normalizeString(value: unknown): unknown { + if (typeof value !== "string") { + return value; + } + + return value.trim(); +} + +export class CompleteAttachmentDto { + @Transform(({ value }) => normalizeString(value)) + @IsString() + @MinLength(1) + @MaxLength(255) + objectKey!: string; + + @Transform(({ value }) => normalizeString(value)) + @IsOptional() + @IsString() + @MaxLength(100) + bucket?: string; + + @Transform(({ value }) => normalizeString(value)) + @IsString() + @MinLength(1) + @MaxLength(255) + fileName!: string; + + @Transform(({ value }) => normalizeString(value)) + @IsString() + @MinLength(1) + @MaxLength(255) + mimeType!: string; + + @Type(() => Number) + @IsInt() + @Min(1) + @Max(1073741824) + fileSize!: number; + + @IsOptional() + @IsEnum(AttachmentType) + type?: AttachmentType; + + @Transform(({ value }) => normalizeString(value)) + @IsOptional() + @IsString() + @MaxLength(255) + taskId?: string; + + @Transform(({ value }) => normalizeString(value)) + @IsOptional() + @IsString() + @MaxLength(128) + checksum?: string; + + @Type(() => Number) + @IsOptional() + @IsInt() + @Min(1) + @Max(100000) + width?: number; + + @Type(() => Number) + @IsOptional() + @IsInt() + @Min(1) + @Max(100000) + height?: number; + + @Type(() => Number) + @IsOptional() + @IsInt() + @Min(1) + @Max(86400000) + durationMs?: number; +} diff --git a/apps/api/src/attachment/dto/presign-attachment.dto.ts b/apps/api/src/attachment/dto/presign-attachment.dto.ts new file mode 100644 index 0000000..9eda1a9 --- /dev/null +++ b/apps/api/src/attachment/dto/presign-attachment.dto.ts @@ -0,0 +1,35 @@ +import { Transform } from "class-transformer"; +import { IsInt, IsOptional, IsString, Max, MaxLength, Min, MinLength } from "class-validator"; + +function normalizeString(value: unknown): unknown { + if (typeof value !== "string") { + return value; + } + + return value.trim(); +} + +export class PresignAttachmentDto { + @Transform(({ value }) => normalizeString(value)) + @IsString() + @MinLength(1) + @MaxLength(255) + fileName!: string; + + @Transform(({ value }) => normalizeString(value)) + @IsString() + @MinLength(1) + @MaxLength(255) + mimeType!: string; + + @IsInt() + @Min(1) + @Max(1073741824) + fileSize!: number; + + @Transform(({ value }) => normalizeString(value)) + @IsOptional() + @IsString() + @MaxLength(255) + taskId?: string; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 051c827..4726407 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,6 +31,12 @@ importers: apps/api: dependencies: + "@aws-sdk/client-s3": + specifier: ^3.1024.0 + version: 3.1024.0 + "@aws-sdk/s3-request-presigner": + specifier: ^3.1024.0 + version: 3.1024.0 "@nestjs/common": specifier: ^11.1.18 version: 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -98,6 +104,299 @@ importers: packages/tsconfig: {} packages: + "@aws-crypto/crc32@5.2.0": + resolution: + { + integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg== + } + engines: { node: ">=16.0.0" } + + "@aws-crypto/crc32c@5.2.0": + resolution: + { + integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag== + } + + "@aws-crypto/sha1-browser@5.2.0": + resolution: + { + integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg== + } + + "@aws-crypto/sha256-browser@5.2.0": + resolution: + { + integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw== + } + + "@aws-crypto/sha256-js@5.2.0": + resolution: + { + integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA== + } + engines: { node: ">=16.0.0" } + + "@aws-crypto/supports-web-crypto@5.2.0": + resolution: + { + integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg== + } + + "@aws-crypto/util@5.2.0": + resolution: + { + integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ== + } + + "@aws-sdk/client-s3@3.1024.0": + resolution: + { + integrity: sha512-8qdO5aLCzaf9l0RdrSBW1iIroRKP2QBqtZ6lkrtHKiaaH0B18xEn+lrEgiN/eCf3uRAYk4cqbnI2XcWzm+7dDQ== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/core@3.973.26": + resolution: + { + integrity: sha512-A/E6n2W42ruU+sfWk+mMUOyVXbsSgGrY3MJ9/0Az5qUdG67y8I6HYzzoAa+e/lzxxl1uCYmEL6BTMi9ZiZnplQ== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/crc64-nvme@3.972.5": + resolution: + { + integrity: sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/credential-provider-env@3.972.24": + resolution: + { + integrity: sha512-FWg8uFmT6vQM7VuzELzwVo5bzExGaKHdubn0StjgrcU5FvuLExUe+k06kn/40uKv59rYzhez8eFNM4yYE/Yb/w== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/credential-provider-http@3.972.26": + resolution: + { + integrity: sha512-CY4ppZ+qHYqcXqBVi//sdHST1QK3KzOEiLtpLsc9W2k2vfZPKExGaQIsOwcyvjpjUEolotitmd3mUNY56IwDEA== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/credential-provider-ini@3.972.28": + resolution: + { + integrity: sha512-wXYvq3+uQcZV7k+bE4yDXCTBdzWTU9x/nMiKBfzInmv6yYK1veMK0AKvRfRBd72nGWYKcL6AxwiPg9z/pYlgpw== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/credential-provider-login@3.972.28": + resolution: + { + integrity: sha512-ZSTfO6jqUTCysbdBPtEX5OUR//3rbD0lN7jO3sQeS2Gjr/Y+DT6SbIJ0oT2cemNw3UzKu97sNONd1CwNMthuZQ== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/credential-provider-node@3.972.29": + resolution: + { + integrity: sha512-clSzDcvndpFJAggLDnDb36sPdlZYyEs5Zm6zgZjjUhwsJgSWiWKwFIXUVBcbruidNyBdbpOv2tNDL9sX8y3/0g== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/credential-provider-process@3.972.24": + resolution: + { + integrity: sha512-Q2k/XLrFXhEztPHqj4SLCNID3hEPdlhh1CDLBpNnM+1L8fq7P+yON9/9M1IGN/dA5W45v44ylERfXtDAlmMNmw== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/credential-provider-sso@3.972.28": + resolution: + { + integrity: sha512-IoUlmKMLEITFn1SiCTjPfR6KrE799FBo5baWyk/5Ppar2yXZoUdaRqZzJzK6TcJxx450M8m8DbpddRVYlp5R/A== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/credential-provider-web-identity@3.972.28": + resolution: + { + integrity: sha512-d+6h0SD8GGERzKe27v5rOzNGKOl0D+l0bWJdqrxH8WSQzHzjsQFIAPgIeOTUwBHVsKKwtSxc91K/SWax6XgswQ== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/middleware-bucket-endpoint@3.972.8": + resolution: + { + integrity: sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/middleware-expect-continue@3.972.8": + resolution: + { + integrity: sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/middleware-flexible-checksums@3.974.6": + resolution: + { + integrity: sha512-YckB8k1ejbyCg/g36gUMFLNzE4W5cERIa4MtsdO+wpTmJEP0+TB7okWIt7d8TDOvnb7SwvxJ21E4TGOBxFpSWQ== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/middleware-host-header@3.972.8": + resolution: + { + integrity: sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/middleware-location-constraint@3.972.8": + resolution: + { + integrity: sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/middleware-logger@3.972.8": + resolution: + { + integrity: sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/middleware-recursion-detection@3.972.9": + resolution: + { + integrity: sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/middleware-sdk-s3@3.972.27": + resolution: + { + integrity: sha512-gomO6DZwx+1D/9mbCpcqO5tPBqYBK7DtdgjTIjZ4yvfh/S7ETwAPS0XbJgP2JD8Ycr5CwVrEkV1sFtu3ShXeOw== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/middleware-ssec@3.972.8": + resolution: + { + integrity: sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/middleware-user-agent@3.972.28": + resolution: + { + integrity: sha512-cfWZFlVh7Va9lRay4PN2A9ARFzaBYcA097InT5M2CdRS05ECF5yaz86jET8Wsl2WcyKYEvVr/QNmKtYtafUHtQ== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/nested-clients@3.996.18": + resolution: + { + integrity: sha512-c7ZSIXrESxHKx2Mcopgd8AlzZgoXMr20fkx5ViPWPOLBvmyhw9VwJx/Govg8Ef/IhEon5R9l53Z8fdYSEmp6VA== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/region-config-resolver@3.972.10": + resolution: + { + integrity: sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/s3-request-presigner@3.1024.0": + resolution: + { + integrity: sha512-KNoGsXnTfBxPqrtV2Owd4mrLnhTHRsOz6Hdaz+As5czGMQuPchoRvG1BJzn2NFRGLt/HSJAXVn+G6IhtRIDe+Q== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/signature-v4-multi-region@3.996.15": + resolution: + { + integrity: sha512-Ukw2RpqvaL96CjfH/FgfBmy/ZosHBqoHBCFsN61qGg99F33vpntIVii8aNeh65XuOja73arSduskoa4OJea9RQ== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/token-providers@3.1021.0": + resolution: + { + integrity: sha512-TKY6h9spUk3OLs5v1oAgW9mAeBE3LAGNBwJokLy96wwmd4W2v/tYlXseProyed9ValDj2u1jK/4Rg1T+1NXyJA== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/types@3.973.6": + resolution: + { + integrity: sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/util-arn-parser@3.972.3": + resolution: + { + integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/util-endpoints@3.996.5": + resolution: + { + integrity: sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/util-format-url@3.972.8": + resolution: + { + integrity: sha512-J6DS9oocrgxM8xlUTTmQOuwRF6rnAGEujAN9SAzllcrQmwn5iJ58ogxy3SEhD0Q7JZvlA5jvIXBkpQRqEqlE9A== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/util-locate-window@3.965.5": + resolution: + { + integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ== + } + engines: { node: ">=20.0.0" } + + "@aws-sdk/util-user-agent-browser@3.972.8": + resolution: + { + integrity: sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA== + } + + "@aws-sdk/util-user-agent-node@3.973.14": + resolution: + { + integrity: sha512-vNSB/DYaPOyujVZBg/zUznH9QC142MaTHVmaFlF7uzzfg3CgT9f/l4C0Yi+vU/tbBhxVcXVB90Oohk5+o+ZbWw== + } + engines: { node: ">=20.0.0" } + peerDependencies: + aws-crt: ">=1.0.0" + peerDependenciesMeta: + aws-crt: + optional: true + + "@aws-sdk/xml-builder@3.972.16": + resolution: + { + integrity: sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A== + } + engines: { node: ">=20.0.0" } + + "@aws/lambda-invoke-store@0.2.4": + resolution: + { + integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ== + } + engines: { node: ">=18.0.0" } + "@borewit/text-codec@0.2.2": resolution: { @@ -622,6 +921,377 @@ packages: integrity: sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w== } + "@smithy/chunked-blob-reader-native@4.2.3": + resolution: + { + integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw== + } + engines: { node: ">=18.0.0" } + + "@smithy/chunked-blob-reader@5.2.2": + resolution: + { + integrity: sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw== + } + engines: { node: ">=18.0.0" } + + "@smithy/config-resolver@4.4.13": + resolution: + { + integrity: sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg== + } + engines: { node: ">=18.0.0" } + + "@smithy/core@3.23.13": + resolution: + { + integrity: sha512-J+2TT9D6oGsUVXVEMvz8h2EmdVnkBiy2auCie4aSJMvKlzUtO5hqjEzXhoCUkIMo7gAYjbQcN0g/MMSXEhDs1Q== + } + engines: { node: ">=18.0.0" } + + "@smithy/credential-provider-imds@4.2.12": + resolution: + { + integrity: sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg== + } + engines: { node: ">=18.0.0" } + + "@smithy/eventstream-codec@4.2.12": + resolution: + { + integrity: sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA== + } + engines: { node: ">=18.0.0" } + + "@smithy/eventstream-serde-browser@4.2.12": + resolution: + { + integrity: sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A== + } + engines: { node: ">=18.0.0" } + + "@smithy/eventstream-serde-config-resolver@4.3.12": + resolution: + { + integrity: sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q== + } + engines: { node: ">=18.0.0" } + + "@smithy/eventstream-serde-node@4.2.12": + resolution: + { + integrity: sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA== + } + engines: { node: ">=18.0.0" } + + "@smithy/eventstream-serde-universal@4.2.12": + resolution: + { + integrity: sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ== + } + engines: { node: ">=18.0.0" } + + "@smithy/fetch-http-handler@5.3.15": + resolution: + { + integrity: sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A== + } + engines: { node: ">=18.0.0" } + + "@smithy/hash-blob-browser@4.2.13": + resolution: + { + integrity: sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g== + } + engines: { node: ">=18.0.0" } + + "@smithy/hash-node@4.2.12": + resolution: + { + integrity: sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w== + } + engines: { node: ">=18.0.0" } + + "@smithy/hash-stream-node@4.2.12": + resolution: + { + integrity: sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw== + } + engines: { node: ">=18.0.0" } + + "@smithy/invalid-dependency@4.2.12": + resolution: + { + integrity: sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g== + } + engines: { node: ">=18.0.0" } + + "@smithy/is-array-buffer@2.2.0": + resolution: + { + integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA== + } + engines: { node: ">=14.0.0" } + + "@smithy/is-array-buffer@4.2.2": + resolution: + { + integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow== + } + engines: { node: ">=18.0.0" } + + "@smithy/md5-js@4.2.12": + resolution: + { + integrity: sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ== + } + engines: { node: ">=18.0.0" } + + "@smithy/middleware-content-length@4.2.12": + resolution: + { + integrity: sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA== + } + engines: { node: ">=18.0.0" } + + "@smithy/middleware-endpoint@4.4.28": + resolution: + { + integrity: sha512-p1gfYpi91CHcs5cBq982UlGlDrxoYUX6XdHSo91cQ2KFuz6QloHosO7Jc60pJiVmkWrKOV8kFYlGFFbQ2WUKKQ== + } + engines: { node: ">=18.0.0" } + + "@smithy/middleware-retry@4.4.46": + resolution: + { + integrity: sha512-SpvWNNOPOrKQGUqZbEPO+es+FRXMWvIyzUKUOYdDgdlA6BdZj/R58p4umoQ76c2oJC44PiM7mKizyyex1IJzow== + } + engines: { node: ">=18.0.0" } + + "@smithy/middleware-serde@4.2.16": + resolution: + { + integrity: sha512-beqfV+RZ9RSv+sQqor3xroUUYgRFCGRw6niGstPG8zO9LgTl0B0MCucxjmrH/2WwksQN7UUgI7KNANoZv+KALA== + } + engines: { node: ">=18.0.0" } + + "@smithy/middleware-stack@4.2.12": + resolution: + { + integrity: sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw== + } + engines: { node: ">=18.0.0" } + + "@smithy/node-config-provider@4.3.12": + resolution: + { + integrity: sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw== + } + engines: { node: ">=18.0.0" } + + "@smithy/node-http-handler@4.5.1": + resolution: + { + integrity: sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw== + } + engines: { node: ">=18.0.0" } + + "@smithy/property-provider@4.2.12": + resolution: + { + integrity: sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A== + } + engines: { node: ">=18.0.0" } + + "@smithy/protocol-http@5.3.12": + resolution: + { + integrity: sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw== + } + engines: { node: ">=18.0.0" } + + "@smithy/querystring-builder@4.2.12": + resolution: + { + integrity: sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg== + } + engines: { node: ">=18.0.0" } + + "@smithy/querystring-parser@4.2.12": + resolution: + { + integrity: sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw== + } + engines: { node: ">=18.0.0" } + + "@smithy/service-error-classification@4.2.12": + resolution: + { + integrity: sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ== + } + engines: { node: ">=18.0.0" } + + "@smithy/shared-ini-file-loader@4.4.7": + resolution: + { + integrity: sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw== + } + engines: { node: ">=18.0.0" } + + "@smithy/signature-v4@5.3.12": + resolution: + { + integrity: sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw== + } + engines: { node: ">=18.0.0" } + + "@smithy/smithy-client@4.12.8": + resolution: + { + integrity: sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA== + } + engines: { node: ">=18.0.0" } + + "@smithy/types@4.13.1": + resolution: + { + integrity: sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g== + } + engines: { node: ">=18.0.0" } + + "@smithy/url-parser@4.2.12": + resolution: + { + integrity: sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-base64@4.3.2": + resolution: + { + integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-body-length-browser@4.2.2": + resolution: + { + integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-body-length-node@4.2.3": + resolution: + { + integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-buffer-from@2.2.0": + resolution: + { + integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA== + } + engines: { node: ">=14.0.0" } + + "@smithy/util-buffer-from@4.2.2": + resolution: + { + integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-config-provider@4.2.2": + resolution: + { + integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-defaults-mode-browser@4.3.44": + resolution: + { + integrity: sha512-eZg6XzaCbVr2S5cAErU5eGBDaOVTuTo1I65i4tQcHENRcZ8rMWhQy1DaIYUSLyZjsfXvmCqZrstSMYyGFocvHA== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-defaults-mode-node@4.2.48": + resolution: + { + integrity: sha512-FqOKTlqSaoV3nzO55pMs5NBnZX8EhoI0DGmn9kbYeXWppgHD6dchyuj2HLqp4INJDJbSrj6OFYJkAh/WhSzZPg== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-endpoints@3.3.3": + resolution: + { + integrity: sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-hex-encoding@4.2.2": + resolution: + { + integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-middleware@4.2.12": + resolution: + { + integrity: sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-retry@4.2.13": + resolution: + { + integrity: sha512-qQQsIvL0MGIbUjeSrg0/VlQ3jGNKyM3/2iU3FPNgy01z+Sp4OvcaxbgIoFOTvB61ZoohtutuOvOcgmhbD0katQ== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-stream@4.5.21": + resolution: + { + integrity: sha512-KzSg+7KKywLnkoKejRtIBXDmwBfjGvg1U1i/etkC7XSWUyFCoLno1IohV2c74IzQqdhX5y3uE44r/8/wuK+A7Q== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-uri-escape@4.2.2": + resolution: + { + integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-utf8@2.3.0": + resolution: + { + integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A== + } + engines: { node: ">=14.0.0" } + + "@smithy/util-utf8@4.2.2": + resolution: + { + integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw== + } + engines: { node: ">=18.0.0" } + + "@smithy/util-waiter@4.2.14": + resolution: + { + integrity: sha512-2zqq5o/oizvMaFUlNiTyZ7dbgYv1a893aGut2uaxtbzTx/VYYnRxWzDHuD/ftgcw94ffenua+ZNLrbqwUYE+Bg== + } + engines: { node: ">=18.0.0" } + + "@smithy/uuid@1.1.2": + resolution: + { + integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g== + } + engines: { node: ">=18.0.0" } + "@standard-schema/spec@1.1.0": resolution: { @@ -974,6 +1644,12 @@ packages: } engines: { node: ">=18" } + bowser@2.14.1: + resolution: + { + integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg== + } + brace-expansion@1.1.13: resolution: { @@ -1507,6 +2183,19 @@ packages: integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== } + fast-xml-builder@1.1.4: + resolution: + { + integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg== + } + + fast-xml-parser@5.5.8: + resolution: + { + integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ== + } + hasBin: true + file-entry-cache@8.0.0: resolution: { @@ -2249,6 +2938,13 @@ packages: } engines: { node: ">=8" } + path-expression-matcher@1.2.1: + resolution: + { + integrity: sha512-d7gQQmLvAKXKXE2GeP9apIGbMYKz88zWdsn/BN2HRWVQsDFdUY36WSLTY0Jvd4HWi7Fb30gQ62oAOzdgJA6fZw== + } + engines: { node: ">=14.0.0" } + path-is-absolute@1.0.1: resolution: { @@ -2723,6 +3419,12 @@ packages: } engines: { node: ">=0.10.0" } + strnum@2.2.2: + resolution: + { + integrity: sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA== + } + strtok3@10.3.5: resolution: { @@ -2998,6 +3700,468 @@ packages: } snapshots: + "@aws-crypto/crc32@5.2.0": + dependencies: + "@aws-crypto/util": 5.2.0 + "@aws-sdk/types": 3.973.6 + tslib: 2.8.1 + + "@aws-crypto/crc32c@5.2.0": + dependencies: + "@aws-crypto/util": 5.2.0 + "@aws-sdk/types": 3.973.6 + tslib: 2.8.1 + + "@aws-crypto/sha1-browser@5.2.0": + dependencies: + "@aws-crypto/supports-web-crypto": 5.2.0 + "@aws-crypto/util": 5.2.0 + "@aws-sdk/types": 3.973.6 + "@aws-sdk/util-locate-window": 3.965.5 + "@smithy/util-utf8": 2.3.0 + tslib: 2.8.1 + + "@aws-crypto/sha256-browser@5.2.0": + dependencies: + "@aws-crypto/sha256-js": 5.2.0 + "@aws-crypto/supports-web-crypto": 5.2.0 + "@aws-crypto/util": 5.2.0 + "@aws-sdk/types": 3.973.6 + "@aws-sdk/util-locate-window": 3.965.5 + "@smithy/util-utf8": 2.3.0 + tslib: 2.8.1 + + "@aws-crypto/sha256-js@5.2.0": + dependencies: + "@aws-crypto/util": 5.2.0 + "@aws-sdk/types": 3.973.6 + tslib: 2.8.1 + + "@aws-crypto/supports-web-crypto@5.2.0": + dependencies: + tslib: 2.8.1 + + "@aws-crypto/util@5.2.0": + dependencies: + "@aws-sdk/types": 3.973.6 + "@smithy/util-utf8": 2.3.0 + tslib: 2.8.1 + + "@aws-sdk/client-s3@3.1024.0": + dependencies: + "@aws-crypto/sha1-browser": 5.2.0 + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.973.26 + "@aws-sdk/credential-provider-node": 3.972.29 + "@aws-sdk/middleware-bucket-endpoint": 3.972.8 + "@aws-sdk/middleware-expect-continue": 3.972.8 + "@aws-sdk/middleware-flexible-checksums": 3.974.6 + "@aws-sdk/middleware-host-header": 3.972.8 + "@aws-sdk/middleware-location-constraint": 3.972.8 + "@aws-sdk/middleware-logger": 3.972.8 + "@aws-sdk/middleware-recursion-detection": 3.972.9 + "@aws-sdk/middleware-sdk-s3": 3.972.27 + "@aws-sdk/middleware-ssec": 3.972.8 + "@aws-sdk/middleware-user-agent": 3.972.28 + "@aws-sdk/region-config-resolver": 3.972.10 + "@aws-sdk/signature-v4-multi-region": 3.996.15 + "@aws-sdk/types": 3.973.6 + "@aws-sdk/util-endpoints": 3.996.5 + "@aws-sdk/util-user-agent-browser": 3.972.8 + "@aws-sdk/util-user-agent-node": 3.973.14 + "@smithy/config-resolver": 4.4.13 + "@smithy/core": 3.23.13 + "@smithy/eventstream-serde-browser": 4.2.12 + "@smithy/eventstream-serde-config-resolver": 4.3.12 + "@smithy/eventstream-serde-node": 4.2.12 + "@smithy/fetch-http-handler": 5.3.15 + "@smithy/hash-blob-browser": 4.2.13 + "@smithy/hash-node": 4.2.12 + "@smithy/hash-stream-node": 4.2.12 + "@smithy/invalid-dependency": 4.2.12 + "@smithy/md5-js": 4.2.12 + "@smithy/middleware-content-length": 4.2.12 + "@smithy/middleware-endpoint": 4.4.28 + "@smithy/middleware-retry": 4.4.46 + "@smithy/middleware-serde": 4.2.16 + "@smithy/middleware-stack": 4.2.12 + "@smithy/node-config-provider": 4.3.12 + "@smithy/node-http-handler": 4.5.1 + "@smithy/protocol-http": 5.3.12 + "@smithy/smithy-client": 4.12.8 + "@smithy/types": 4.13.1 + "@smithy/url-parser": 4.2.12 + "@smithy/util-base64": 4.3.2 + "@smithy/util-body-length-browser": 4.2.2 + "@smithy/util-body-length-node": 4.2.3 + "@smithy/util-defaults-mode-browser": 4.3.44 + "@smithy/util-defaults-mode-node": 4.2.48 + "@smithy/util-endpoints": 3.3.3 + "@smithy/util-middleware": 4.2.12 + "@smithy/util-retry": 4.2.13 + "@smithy/util-stream": 4.5.21 + "@smithy/util-utf8": 4.2.2 + "@smithy/util-waiter": 4.2.14 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + "@aws-sdk/core@3.973.26": + dependencies: + "@aws-sdk/types": 3.973.6 + "@aws-sdk/xml-builder": 3.972.16 + "@smithy/core": 3.23.13 + "@smithy/node-config-provider": 4.3.12 + "@smithy/property-provider": 4.2.12 + "@smithy/protocol-http": 5.3.12 + "@smithy/signature-v4": 5.3.12 + "@smithy/smithy-client": 4.12.8 + "@smithy/types": 4.13.1 + "@smithy/util-base64": 4.3.2 + "@smithy/util-middleware": 4.2.12 + "@smithy/util-utf8": 4.2.2 + tslib: 2.8.1 + + "@aws-sdk/crc64-nvme@3.972.5": + dependencies: + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/credential-provider-env@3.972.24": + dependencies: + "@aws-sdk/core": 3.973.26 + "@aws-sdk/types": 3.973.6 + "@smithy/property-provider": 4.2.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/credential-provider-http@3.972.26": + dependencies: + "@aws-sdk/core": 3.973.26 + "@aws-sdk/types": 3.973.6 + "@smithy/fetch-http-handler": 5.3.15 + "@smithy/node-http-handler": 4.5.1 + "@smithy/property-provider": 4.2.12 + "@smithy/protocol-http": 5.3.12 + "@smithy/smithy-client": 4.12.8 + "@smithy/types": 4.13.1 + "@smithy/util-stream": 4.5.21 + tslib: 2.8.1 + + "@aws-sdk/credential-provider-ini@3.972.28": + dependencies: + "@aws-sdk/core": 3.973.26 + "@aws-sdk/credential-provider-env": 3.972.24 + "@aws-sdk/credential-provider-http": 3.972.26 + "@aws-sdk/credential-provider-login": 3.972.28 + "@aws-sdk/credential-provider-process": 3.972.24 + "@aws-sdk/credential-provider-sso": 3.972.28 + "@aws-sdk/credential-provider-web-identity": 3.972.28 + "@aws-sdk/nested-clients": 3.996.18 + "@aws-sdk/types": 3.973.6 + "@smithy/credential-provider-imds": 4.2.12 + "@smithy/property-provider": 4.2.12 + "@smithy/shared-ini-file-loader": 4.4.7 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + "@aws-sdk/credential-provider-login@3.972.28": + dependencies: + "@aws-sdk/core": 3.973.26 + "@aws-sdk/nested-clients": 3.996.18 + "@aws-sdk/types": 3.973.6 + "@smithy/property-provider": 4.2.12 + "@smithy/protocol-http": 5.3.12 + "@smithy/shared-ini-file-loader": 4.4.7 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + "@aws-sdk/credential-provider-node@3.972.29": + dependencies: + "@aws-sdk/credential-provider-env": 3.972.24 + "@aws-sdk/credential-provider-http": 3.972.26 + "@aws-sdk/credential-provider-ini": 3.972.28 + "@aws-sdk/credential-provider-process": 3.972.24 + "@aws-sdk/credential-provider-sso": 3.972.28 + "@aws-sdk/credential-provider-web-identity": 3.972.28 + "@aws-sdk/types": 3.973.6 + "@smithy/credential-provider-imds": 4.2.12 + "@smithy/property-provider": 4.2.12 + "@smithy/shared-ini-file-loader": 4.4.7 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + "@aws-sdk/credential-provider-process@3.972.24": + dependencies: + "@aws-sdk/core": 3.973.26 + "@aws-sdk/types": 3.973.6 + "@smithy/property-provider": 4.2.12 + "@smithy/shared-ini-file-loader": 4.4.7 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/credential-provider-sso@3.972.28": + dependencies: + "@aws-sdk/core": 3.973.26 + "@aws-sdk/nested-clients": 3.996.18 + "@aws-sdk/token-providers": 3.1021.0 + "@aws-sdk/types": 3.973.6 + "@smithy/property-provider": 4.2.12 + "@smithy/shared-ini-file-loader": 4.4.7 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + "@aws-sdk/credential-provider-web-identity@3.972.28": + dependencies: + "@aws-sdk/core": 3.973.26 + "@aws-sdk/nested-clients": 3.996.18 + "@aws-sdk/types": 3.973.6 + "@smithy/property-provider": 4.2.12 + "@smithy/shared-ini-file-loader": 4.4.7 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + "@aws-sdk/middleware-bucket-endpoint@3.972.8": + dependencies: + "@aws-sdk/types": 3.973.6 + "@aws-sdk/util-arn-parser": 3.972.3 + "@smithy/node-config-provider": 4.3.12 + "@smithy/protocol-http": 5.3.12 + "@smithy/types": 4.13.1 + "@smithy/util-config-provider": 4.2.2 + tslib: 2.8.1 + + "@aws-sdk/middleware-expect-continue@3.972.8": + dependencies: + "@aws-sdk/types": 3.973.6 + "@smithy/protocol-http": 5.3.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/middleware-flexible-checksums@3.974.6": + dependencies: + "@aws-crypto/crc32": 5.2.0 + "@aws-crypto/crc32c": 5.2.0 + "@aws-crypto/util": 5.2.0 + "@aws-sdk/core": 3.973.26 + "@aws-sdk/crc64-nvme": 3.972.5 + "@aws-sdk/types": 3.973.6 + "@smithy/is-array-buffer": 4.2.2 + "@smithy/node-config-provider": 4.3.12 + "@smithy/protocol-http": 5.3.12 + "@smithy/types": 4.13.1 + "@smithy/util-middleware": 4.2.12 + "@smithy/util-stream": 4.5.21 + "@smithy/util-utf8": 4.2.2 + tslib: 2.8.1 + + "@aws-sdk/middleware-host-header@3.972.8": + dependencies: + "@aws-sdk/types": 3.973.6 + "@smithy/protocol-http": 5.3.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/middleware-location-constraint@3.972.8": + dependencies: + "@aws-sdk/types": 3.973.6 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/middleware-logger@3.972.8": + dependencies: + "@aws-sdk/types": 3.973.6 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/middleware-recursion-detection@3.972.9": + dependencies: + "@aws-sdk/types": 3.973.6 + "@aws/lambda-invoke-store": 0.2.4 + "@smithy/protocol-http": 5.3.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/middleware-sdk-s3@3.972.27": + dependencies: + "@aws-sdk/core": 3.973.26 + "@aws-sdk/types": 3.973.6 + "@aws-sdk/util-arn-parser": 3.972.3 + "@smithy/core": 3.23.13 + "@smithy/node-config-provider": 4.3.12 + "@smithy/protocol-http": 5.3.12 + "@smithy/signature-v4": 5.3.12 + "@smithy/smithy-client": 4.12.8 + "@smithy/types": 4.13.1 + "@smithy/util-config-provider": 4.2.2 + "@smithy/util-middleware": 4.2.12 + "@smithy/util-stream": 4.5.21 + "@smithy/util-utf8": 4.2.2 + tslib: 2.8.1 + + "@aws-sdk/middleware-ssec@3.972.8": + dependencies: + "@aws-sdk/types": 3.973.6 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/middleware-user-agent@3.972.28": + dependencies: + "@aws-sdk/core": 3.973.26 + "@aws-sdk/types": 3.973.6 + "@aws-sdk/util-endpoints": 3.996.5 + "@smithy/core": 3.23.13 + "@smithy/protocol-http": 5.3.12 + "@smithy/types": 4.13.1 + "@smithy/util-retry": 4.2.13 + tslib: 2.8.1 + + "@aws-sdk/nested-clients@3.996.18": + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.973.26 + "@aws-sdk/middleware-host-header": 3.972.8 + "@aws-sdk/middleware-logger": 3.972.8 + "@aws-sdk/middleware-recursion-detection": 3.972.9 + "@aws-sdk/middleware-user-agent": 3.972.28 + "@aws-sdk/region-config-resolver": 3.972.10 + "@aws-sdk/types": 3.973.6 + "@aws-sdk/util-endpoints": 3.996.5 + "@aws-sdk/util-user-agent-browser": 3.972.8 + "@aws-sdk/util-user-agent-node": 3.973.14 + "@smithy/config-resolver": 4.4.13 + "@smithy/core": 3.23.13 + "@smithy/fetch-http-handler": 5.3.15 + "@smithy/hash-node": 4.2.12 + "@smithy/invalid-dependency": 4.2.12 + "@smithy/middleware-content-length": 4.2.12 + "@smithy/middleware-endpoint": 4.4.28 + "@smithy/middleware-retry": 4.4.46 + "@smithy/middleware-serde": 4.2.16 + "@smithy/middleware-stack": 4.2.12 + "@smithy/node-config-provider": 4.3.12 + "@smithy/node-http-handler": 4.5.1 + "@smithy/protocol-http": 5.3.12 + "@smithy/smithy-client": 4.12.8 + "@smithy/types": 4.13.1 + "@smithy/url-parser": 4.2.12 + "@smithy/util-base64": 4.3.2 + "@smithy/util-body-length-browser": 4.2.2 + "@smithy/util-body-length-node": 4.2.3 + "@smithy/util-defaults-mode-browser": 4.3.44 + "@smithy/util-defaults-mode-node": 4.2.48 + "@smithy/util-endpoints": 3.3.3 + "@smithy/util-middleware": 4.2.12 + "@smithy/util-retry": 4.2.13 + "@smithy/util-utf8": 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + "@aws-sdk/region-config-resolver@3.972.10": + dependencies: + "@aws-sdk/types": 3.973.6 + "@smithy/config-resolver": 4.4.13 + "@smithy/node-config-provider": 4.3.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/s3-request-presigner@3.1024.0": + dependencies: + "@aws-sdk/signature-v4-multi-region": 3.996.15 + "@aws-sdk/types": 3.973.6 + "@aws-sdk/util-format-url": 3.972.8 + "@smithy/middleware-endpoint": 4.4.28 + "@smithy/protocol-http": 5.3.12 + "@smithy/smithy-client": 4.12.8 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/signature-v4-multi-region@3.996.15": + dependencies: + "@aws-sdk/middleware-sdk-s3": 3.972.27 + "@aws-sdk/types": 3.973.6 + "@smithy/protocol-http": 5.3.12 + "@smithy/signature-v4": 5.3.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/token-providers@3.1021.0": + dependencies: + "@aws-sdk/core": 3.973.26 + "@aws-sdk/nested-clients": 3.996.18 + "@aws-sdk/types": 3.973.6 + "@smithy/property-provider": 4.2.12 + "@smithy/shared-ini-file-loader": 4.4.7 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + "@aws-sdk/types@3.973.6": + dependencies: + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/util-arn-parser@3.972.3": + dependencies: + tslib: 2.8.1 + + "@aws-sdk/util-endpoints@3.996.5": + dependencies: + "@aws-sdk/types": 3.973.6 + "@smithy/types": 4.13.1 + "@smithy/url-parser": 4.2.12 + "@smithy/util-endpoints": 3.3.3 + tslib: 2.8.1 + + "@aws-sdk/util-format-url@3.972.8": + dependencies: + "@aws-sdk/types": 3.973.6 + "@smithy/querystring-builder": 4.2.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@aws-sdk/util-locate-window@3.965.5": + dependencies: + tslib: 2.8.1 + + "@aws-sdk/util-user-agent-browser@3.972.8": + dependencies: + "@aws-sdk/types": 3.973.6 + "@smithy/types": 4.13.1 + bowser: 2.14.1 + tslib: 2.8.1 + + "@aws-sdk/util-user-agent-node@3.973.14": + dependencies: + "@aws-sdk/middleware-user-agent": 3.972.28 + "@aws-sdk/types": 3.973.6 + "@smithy/node-config-provider": 4.3.12 + "@smithy/types": 4.13.1 + "@smithy/util-config-provider": 4.2.2 + tslib: 2.8.1 + + "@aws-sdk/xml-builder@3.972.16": + dependencies: + "@smithy/types": 4.13.1 + fast-xml-parser: 5.5.8 + tslib: 2.8.1 + + "@aws/lambda-invoke-store@0.2.4": {} + "@borewit/text-codec@0.2.2": {} "@clack/core@0.5.0": @@ -3334,6 +4498,338 @@ snapshots: "@scure/base@2.0.0": {} + "@smithy/chunked-blob-reader-native@4.2.3": + dependencies: + "@smithy/util-base64": 4.3.2 + tslib: 2.8.1 + + "@smithy/chunked-blob-reader@5.2.2": + dependencies: + tslib: 2.8.1 + + "@smithy/config-resolver@4.4.13": + dependencies: + "@smithy/node-config-provider": 4.3.12 + "@smithy/types": 4.13.1 + "@smithy/util-config-provider": 4.2.2 + "@smithy/util-endpoints": 3.3.3 + "@smithy/util-middleware": 4.2.12 + tslib: 2.8.1 + + "@smithy/core@3.23.13": + dependencies: + "@smithy/protocol-http": 5.3.12 + "@smithy/types": 4.13.1 + "@smithy/url-parser": 4.2.12 + "@smithy/util-base64": 4.3.2 + "@smithy/util-body-length-browser": 4.2.2 + "@smithy/util-middleware": 4.2.12 + "@smithy/util-stream": 4.5.21 + "@smithy/util-utf8": 4.2.2 + "@smithy/uuid": 1.1.2 + tslib: 2.8.1 + + "@smithy/credential-provider-imds@4.2.12": + dependencies: + "@smithy/node-config-provider": 4.3.12 + "@smithy/property-provider": 4.2.12 + "@smithy/types": 4.13.1 + "@smithy/url-parser": 4.2.12 + tslib: 2.8.1 + + "@smithy/eventstream-codec@4.2.12": + dependencies: + "@aws-crypto/crc32": 5.2.0 + "@smithy/types": 4.13.1 + "@smithy/util-hex-encoding": 4.2.2 + tslib: 2.8.1 + + "@smithy/eventstream-serde-browser@4.2.12": + dependencies: + "@smithy/eventstream-serde-universal": 4.2.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/eventstream-serde-config-resolver@4.3.12": + dependencies: + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/eventstream-serde-node@4.2.12": + dependencies: + "@smithy/eventstream-serde-universal": 4.2.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/eventstream-serde-universal@4.2.12": + dependencies: + "@smithy/eventstream-codec": 4.2.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/fetch-http-handler@5.3.15": + dependencies: + "@smithy/protocol-http": 5.3.12 + "@smithy/querystring-builder": 4.2.12 + "@smithy/types": 4.13.1 + "@smithy/util-base64": 4.3.2 + tslib: 2.8.1 + + "@smithy/hash-blob-browser@4.2.13": + dependencies: + "@smithy/chunked-blob-reader": 5.2.2 + "@smithy/chunked-blob-reader-native": 4.2.3 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/hash-node@4.2.12": + dependencies: + "@smithy/types": 4.13.1 + "@smithy/util-buffer-from": 4.2.2 + "@smithy/util-utf8": 4.2.2 + tslib: 2.8.1 + + "@smithy/hash-stream-node@4.2.12": + dependencies: + "@smithy/types": 4.13.1 + "@smithy/util-utf8": 4.2.2 + tslib: 2.8.1 + + "@smithy/invalid-dependency@4.2.12": + dependencies: + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/is-array-buffer@2.2.0": + dependencies: + tslib: 2.8.1 + + "@smithy/is-array-buffer@4.2.2": + dependencies: + tslib: 2.8.1 + + "@smithy/md5-js@4.2.12": + dependencies: + "@smithy/types": 4.13.1 + "@smithy/util-utf8": 4.2.2 + tslib: 2.8.1 + + "@smithy/middleware-content-length@4.2.12": + dependencies: + "@smithy/protocol-http": 5.3.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/middleware-endpoint@4.4.28": + dependencies: + "@smithy/core": 3.23.13 + "@smithy/middleware-serde": 4.2.16 + "@smithy/node-config-provider": 4.3.12 + "@smithy/shared-ini-file-loader": 4.4.7 + "@smithy/types": 4.13.1 + "@smithy/url-parser": 4.2.12 + "@smithy/util-middleware": 4.2.12 + tslib: 2.8.1 + + "@smithy/middleware-retry@4.4.46": + dependencies: + "@smithy/node-config-provider": 4.3.12 + "@smithy/protocol-http": 5.3.12 + "@smithy/service-error-classification": 4.2.12 + "@smithy/smithy-client": 4.12.8 + "@smithy/types": 4.13.1 + "@smithy/util-middleware": 4.2.12 + "@smithy/util-retry": 4.2.13 + "@smithy/uuid": 1.1.2 + tslib: 2.8.1 + + "@smithy/middleware-serde@4.2.16": + dependencies: + "@smithy/core": 3.23.13 + "@smithy/protocol-http": 5.3.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/middleware-stack@4.2.12": + dependencies: + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/node-config-provider@4.3.12": + dependencies: + "@smithy/property-provider": 4.2.12 + "@smithy/shared-ini-file-loader": 4.4.7 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/node-http-handler@4.5.1": + dependencies: + "@smithy/protocol-http": 5.3.12 + "@smithy/querystring-builder": 4.2.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/property-provider@4.2.12": + dependencies: + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/protocol-http@5.3.12": + dependencies: + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/querystring-builder@4.2.12": + dependencies: + "@smithy/types": 4.13.1 + "@smithy/util-uri-escape": 4.2.2 + tslib: 2.8.1 + + "@smithy/querystring-parser@4.2.12": + dependencies: + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/service-error-classification@4.2.12": + dependencies: + "@smithy/types": 4.13.1 + + "@smithy/shared-ini-file-loader@4.4.7": + dependencies: + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/signature-v4@5.3.12": + dependencies: + "@smithy/is-array-buffer": 4.2.2 + "@smithy/protocol-http": 5.3.12 + "@smithy/types": 4.13.1 + "@smithy/util-hex-encoding": 4.2.2 + "@smithy/util-middleware": 4.2.12 + "@smithy/util-uri-escape": 4.2.2 + "@smithy/util-utf8": 4.2.2 + tslib: 2.8.1 + + "@smithy/smithy-client@4.12.8": + dependencies: + "@smithy/core": 3.23.13 + "@smithy/middleware-endpoint": 4.4.28 + "@smithy/middleware-stack": 4.2.12 + "@smithy/protocol-http": 5.3.12 + "@smithy/types": 4.13.1 + "@smithy/util-stream": 4.5.21 + tslib: 2.8.1 + + "@smithy/types@4.13.1": + dependencies: + tslib: 2.8.1 + + "@smithy/url-parser@4.2.12": + dependencies: + "@smithy/querystring-parser": 4.2.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/util-base64@4.3.2": + dependencies: + "@smithy/util-buffer-from": 4.2.2 + "@smithy/util-utf8": 4.2.2 + tslib: 2.8.1 + + "@smithy/util-body-length-browser@4.2.2": + dependencies: + tslib: 2.8.1 + + "@smithy/util-body-length-node@4.2.3": + dependencies: + tslib: 2.8.1 + + "@smithy/util-buffer-from@2.2.0": + dependencies: + "@smithy/is-array-buffer": 2.2.0 + tslib: 2.8.1 + + "@smithy/util-buffer-from@4.2.2": + dependencies: + "@smithy/is-array-buffer": 4.2.2 + tslib: 2.8.1 + + "@smithy/util-config-provider@4.2.2": + dependencies: + tslib: 2.8.1 + + "@smithy/util-defaults-mode-browser@4.3.44": + dependencies: + "@smithy/property-provider": 4.2.12 + "@smithy/smithy-client": 4.12.8 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/util-defaults-mode-node@4.2.48": + dependencies: + "@smithy/config-resolver": 4.4.13 + "@smithy/credential-provider-imds": 4.2.12 + "@smithy/node-config-provider": 4.3.12 + "@smithy/property-provider": 4.2.12 + "@smithy/smithy-client": 4.12.8 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/util-endpoints@3.3.3": + dependencies: + "@smithy/node-config-provider": 4.3.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/util-hex-encoding@4.2.2": + dependencies: + tslib: 2.8.1 + + "@smithy/util-middleware@4.2.12": + dependencies: + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/util-retry@4.2.13": + dependencies: + "@smithy/service-error-classification": 4.2.12 + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/util-stream@4.5.21": + dependencies: + "@smithy/fetch-http-handler": 5.3.15 + "@smithy/node-http-handler": 4.5.1 + "@smithy/types": 4.13.1 + "@smithy/util-base64": 4.3.2 + "@smithy/util-buffer-from": 4.2.2 + "@smithy/util-hex-encoding": 4.2.2 + "@smithy/util-utf8": 4.2.2 + tslib: 2.8.1 + + "@smithy/util-uri-escape@4.2.2": + dependencies: + tslib: 2.8.1 + + "@smithy/util-utf8@2.3.0": + dependencies: + "@smithy/util-buffer-from": 2.2.0 + tslib: 2.8.1 + + "@smithy/util-utf8@4.2.2": + dependencies: + "@smithy/util-buffer-from": 4.2.2 + tslib: 2.8.1 + + "@smithy/util-waiter@4.2.14": + dependencies: + "@smithy/types": 4.13.1 + tslib: 2.8.1 + + "@smithy/uuid@1.1.2": + dependencies: + tslib: 2.8.1 + "@standard-schema/spec@1.1.0": {} "@tokenizer/inflate@0.4.1": @@ -3527,6 +5023,8 @@ snapshots: transitivePeerDependencies: - supports-color + bowser@2.14.1: {} + brace-expansion@1.1.13: dependencies: balanced-match: 1.0.2 @@ -3846,6 +5344,16 @@ snapshots: fast-uri@3.1.0: {} + fast-xml-builder@1.1.4: + dependencies: + path-expression-matcher: 1.2.1 + + fast-xml-parser@5.5.8: + dependencies: + fast-xml-builder: 1.1.4 + path-expression-matcher: 1.2.1 + strnum: 2.2.2 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -4251,6 +5759,8 @@ snapshots: path-exists@4.0.0: {} + path-expression-matcher@1.2.1: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -4516,6 +6026,8 @@ snapshots: strip-json-comments@2.0.1: {} + strnum@2.2.2: {} + strtok3@10.3.5: dependencies: "@tokenizer/token": 0.3.0 From 32022c14374bb36f5a3061bc21563822d0816215 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 00:08:27 +0800 Subject: [PATCH 23/80] feat(api-quota): enforce user storage quota checks --- apps/api/src/attachment/attachment.service.ts | 103 +++++++++++++++--- 1 file changed, 89 insertions(+), 14 deletions(-) diff --git a/apps/api/src/attachment/attachment.service.ts b/apps/api/src/attachment/attachment.service.ts index 7ddd5b5..031cd03 100644 --- a/apps/api/src/attachment/attachment.service.ts +++ b/apps/api/src/attachment/attachment.service.ts @@ -1,5 +1,5 @@ import { randomUUID } from "node:crypto"; -import { Injectable, NotFoundException } from "@nestjs/common"; +import { Injectable, NotFoundException, PayloadTooLargeException } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; @@ -8,6 +8,11 @@ import { PrismaService } from "../prisma/prisma.service"; import { CompleteAttachmentDto } from "./dto/complete-attachment.dto"; import { PresignAttachmentDto } from "./dto/presign-attachment.dto"; +type QuotaInfo = { + totalBytes: bigint; + usedBytes: bigint; +}; + export type PresignAttachmentResponse = { method: "PUT"; uploadUrl: string; @@ -15,6 +20,11 @@ export type PresignAttachmentResponse = { objectKey: string; objectUrl: string; expiresInSeconds: number; + quota: { + totalBytes: string; + usedBytes: string; + remainingBytes: string; + }; headers: { "Content-Type": string; }; @@ -49,6 +59,9 @@ export class AttachmentService { userId: string, body: PresignAttachmentDto ): Promise { + const quotaInfo = await this.getQuotaSnapshot(userId); + this.assertQuotaAvailable(quotaInfo.totalBytes, quotaInfo.usedBytes, body.fileSize); + if (body.taskId) { await this.ensureTaskOwnership(userId, body.taskId); } @@ -76,6 +89,11 @@ export class AttachmentService { objectKey, objectUrl, expiresInSeconds, + quota: { + totalBytes: quotaInfo.totalBytes.toString(), + usedBytes: quotaInfo.usedBytes.toString(), + remainingBytes: (quotaInfo.totalBytes - quotaInfo.usedBytes).toString() + }, headers: { "Content-Type": body.mimeType } @@ -92,20 +110,45 @@ export class AttachmentService { const bucket = body.bucket ?? this.getDefaultBucket(); const objectUrl = this.resolveObjectUrl(bucket, body.objectKey); - const attachment = await this.prismaService.attachment.create({ - data: { - userId, - taskId: body.taskId ?? null, - type: body.type ?? this.resolveAttachmentType(body.mimeType), - url: objectUrl, - mimeType: body.mimeType, - fileName: body.fileName, - fileSize: body.fileSize, - width: body.width ?? null, - height: body.height ?? null, - durationMs: body.durationMs ?? null, - checksum: body.checksum ?? null + + const attachment = await this.prismaService.$transaction(async (tx) => { + const quotaInfo = await this.getQuotaSnapshot(userId, tx); + this.assertQuotaAvailable(quotaInfo.totalBytes, quotaInfo.usedBytes, body.fileSize); + + const uploadBytes = BigInt(body.fileSize); + const maxUsedBeforeUpload = quotaInfo.totalBytes - uploadBytes; + const updatedUser = await tx.user.updateMany({ + where: { + id: userId, + usedStorageBytes: { + lte: maxUsedBeforeUpload + } + }, + data: { + usedStorageBytes: { + increment: uploadBytes + } + } + }); + if (updatedUser.count === 0) { + throw new PayloadTooLargeException("存储配额不足"); } + + return tx.attachment.create({ + data: { + userId, + taskId: body.taskId ?? null, + type: body.type ?? this.resolveAttachmentType(body.mimeType), + url: objectUrl, + mimeType: body.mimeType, + fileName: body.fileName, + fileSize: body.fileSize, + width: body.width ?? null, + height: body.height ?? null, + durationMs: body.durationMs ?? null, + checksum: body.checksum ?? null + } + }); }); return { @@ -204,4 +247,36 @@ export class AttachmentService { throw new NotFoundException("任务不存在"); } } + + private async getQuotaSnapshot( + userId: string, + tx: Pick = this.prismaService + ): Promise { + const user = await tx.user.findUnique({ + where: { + id: userId + }, + select: { + id: true, + defaultStorageQuotaMb: true, + usedStorageBytes: true + } + }); + + if (!user) { + throw new NotFoundException("用户不存在"); + } + + return { + totalBytes: BigInt(user.defaultStorageQuotaMb) * 1024n * 1024n, + usedBytes: user.usedStorageBytes + }; + } + + private assertQuotaAvailable(totalBytes: bigint, usedBytes: bigint, fileSize: number): void { + const uploadBytes = BigInt(fileSize); + if (uploadBytes > totalBytes || usedBytes + uploadBytes > totalBytes) { + throw new PayloadTooLargeException("存储配额不足"); + } + } } From 3a9b5fb0005888be898c758a5b3668957d875a18 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 00:13:19 +0800 Subject: [PATCH 24/80] test(api-task): add integration tests for task endpoints --- apps/api/jest.config.cjs | 11 + apps/api/package.json | 8 +- apps/api/test/task.spec.ts | 464 +++++ apps/api/tsconfig.spec.json | 9 + pnpm-lock.yaml | 3574 +++++++++++++++++++++++++++++++++++ 5 files changed, 4065 insertions(+), 1 deletion(-) create mode 100644 apps/api/jest.config.cjs create mode 100644 apps/api/test/task.spec.ts create mode 100644 apps/api/tsconfig.spec.json diff --git a/apps/api/jest.config.cjs b/apps/api/jest.config.cjs new file mode 100644 index 0000000..8194a11 --- /dev/null +++ b/apps/api/jest.config.cjs @@ -0,0 +1,11 @@ +/** @type {import('jest').Config} */ +module.exports = { + rootDir: ".", + testEnvironment: "node", + clearMocks: true, + testMatch: ["/test/**/*.spec.ts"], + moduleFileExtensions: ["ts", "js", "json"], + transform: { + "^.+\\.(t|j)s$": ["ts-jest", { tsconfig: "/tsconfig.spec.json" }] + } +}; diff --git a/apps/api/package.json b/apps/api/package.json index 5cda8d9..2b3fe6d 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -10,15 +10,21 @@ "start:dev": "ts-node-dev --respawn --transpile-only src/main.ts", "build": "tsc -p tsconfig.build.json", "typecheck": "tsc --noEmit -p tsconfig.json", - "test": "echo api tests pending" + "test": "jest --config jest.config.cjs --runInBand" }, "license": "GPL-3.0-or-later", "devDependencies": { + "@nestjs/testing": "^11.1.18", + "@types/jest": "^30.0.0", "@types/node": "^25.5.2", "@types/passport-github2": "^1.2.9", "@types/passport-oauth2": "^1.8.0", + "@types/supertest": "^7.2.0", "dotenv": "^16.6.1", + "jest": "^30.3.0", "prisma": "^7.6.0", + "supertest": "^7.2.2", + "ts-jest": "^29.4.9", "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", "typescript": "^5.9.3" diff --git a/apps/api/test/task.spec.ts b/apps/api/test/task.spec.ts new file mode 100644 index 0000000..fcc6799 --- /dev/null +++ b/apps/api/test/task.spec.ts @@ -0,0 +1,464 @@ +import request from "supertest"; +import { INestApplication, ValidationPipe } from "@nestjs/common"; +import { Test, TestingModule } from "@nestjs/testing"; +import { PrismaService } from "../src/prisma/prisma.service"; +import { TaskController } from "../src/task/task.controller"; +import { TaskService } from "../src/task/task.service"; +import { TaskPriority, TaskStatus } from "../generated/prisma/client"; + +type TaskRecord = { + id: string; + userId: string; + title: string; + contentJson: unknown | null; + contentText: string | null; + priority: TaskPriority; + status: TaskStatus; + ddl: Date | null; + completedAt: Date | null; + version: number; + createdAt: Date; + updatedAt: Date; +}; + +type TagRecord = { + id: string; + userId: string; + name: string; +}; + +type TaskTagRecord = { + taskId: string; + tagId: string; +}; + +type ListWhereInput = { + userId?: string; + status?: TaskStatus; + priority?: TaskPriority; + taskTags?: { + some: { + tag: { + name: { + in: string[]; + }; + }; + }; + }; + OR?: Array<{ + title?: { + contains: string; + mode?: "insensitive"; + }; + contentText?: { + contains: string; + mode?: "insensitive"; + }; + }>; +}; + +class InMemoryPrismaService { + private taskIdSequence = 1; + private tagIdSequence = 1; + private tasks: TaskRecord[] = []; + private tags: TagRecord[] = []; + private taskTags: TaskTagRecord[] = []; + + readonly task = { + findMany: async (args: { + where?: ListWhereInput; + orderBy?: { createdAt?: "asc" | "desc"; updatedAt?: "asc" | "desc"; ddl?: "asc" | "desc" }; + skip?: number; + take?: number; + }) => { + const where = args.where; + const skip = args.skip ?? 0; + const take = args.take ?? 20; + let filtered = [...this.tasks]; + + if (where?.userId) { + filtered = filtered.filter((task) => task.userId === where.userId); + } + if (where?.status) { + filtered = filtered.filter((task) => task.status === where.status); + } + if (where?.priority) { + filtered = filtered.filter((task) => task.priority === where.priority); + } + if (where?.taskTags?.some.tag.name.in) { + const expectedTags = new Set(where.taskTags.some.tag.name.in); + filtered = filtered.filter((task) => { + const taskTagNames = this.getTaskTagNames(task.id); + return taskTagNames.some((tagName) => expectedTags.has(tagName)); + }); + } + if (where?.OR && where.OR.length > 0) { + filtered = filtered.filter((task) => + where.OR!.some((orCondition) => { + if (orCondition.title?.contains) { + return task.title.toLowerCase().includes(orCondition.title.contains.toLowerCase()); + } + if (orCondition.contentText?.contains) { + return ( + task.contentText + ?.toLowerCase() + .includes(orCondition.contentText.contains.toLowerCase()) ?? false + ); + } + return false; + }) + ); + } + + if (args.orderBy) { + const [orderField, orderDirection] = Object.entries(args.orderBy)[0] as [ + "createdAt" | "updatedAt" | "ddl", + "asc" | "desc" + ]; + filtered.sort((left, right) => { + const leftValue = left[orderField]; + const rightValue = right[orderField]; + + if (leftValue === null && rightValue === null) { + return 0; + } + if (leftValue === null) { + return 1; + } + if (rightValue === null) { + return -1; + } + + const diff = leftValue.getTime() - rightValue.getTime(); + return orderDirection === "asc" ? diff : -diff; + }); + } + + return filtered.slice(skip, skip + take).map((task) => this.toTaskWithTags(task)); + }, + + count: async (args: { where?: ListWhereInput }) => { + const results = await this.task.findMany({ + where: args.where, + skip: 0, + take: Number.MAX_SAFE_INTEGER + }); + return results.length; + }, + + findFirst: async (args: { + where: { + id?: string; + userId?: string; + }; + select?: { + id?: boolean; + status?: boolean; + }; + }) => { + const task = this.tasks.find( + (item) => + (args.where.id === undefined || item.id === args.where.id) && + (args.where.userId === undefined || item.userId === args.where.userId) + ); + if (!task) { + return null; + } + + if (args.select) { + return { + id: args.select.id ? task.id : undefined, + status: args.select.status ? task.status : undefined + }; + } + + return this.toTaskWithTags(task); + }, + + create: async (args: { + data: { + userId: string; + title: string; + contentJson?: unknown; + contentText: string | null; + priority: TaskPriority; + status: TaskStatus; + ddl: Date | null; + completedAt: Date | null; + }; + }) => { + const now = new Date(); + const task: TaskRecord = { + id: `task_${this.taskIdSequence++}`, + userId: args.data.userId, + title: args.data.title, + contentJson: args.data.contentJson ?? null, + contentText: args.data.contentText, + priority: args.data.priority, + status: args.data.status, + ddl: args.data.ddl, + completedAt: args.data.completedAt, + version: 1, + createdAt: now, + updatedAt: now + }; + this.tasks.push(task); + return task; + }, + + update: async (args: { + where: { + id: string; + }; + data: { + title?: string; + contentJson?: unknown; + contentText?: string | null; + priority?: TaskPriority; + status?: TaskStatus; + ddl?: Date | null; + completedAt?: Date | null; + version?: { + increment: number; + }; + }; + }) => { + const task = this.tasks.find((item) => item.id === args.where.id); + if (!task) { + throw new Error("task not found"); + } + + if (args.data.title !== undefined) { + task.title = args.data.title; + } + if (args.data.contentJson !== undefined) { + task.contentJson = args.data.contentJson; + } + if (args.data.contentText !== undefined) { + task.contentText = args.data.contentText; + } + if (args.data.priority !== undefined) { + task.priority = args.data.priority; + } + if (args.data.status !== undefined) { + task.status = args.data.status; + } + if (args.data.ddl !== undefined) { + task.ddl = args.data.ddl; + } + if (args.data.completedAt !== undefined) { + task.completedAt = args.data.completedAt; + } + if (args.data.version !== undefined) { + task.version += args.data.version.increment; + } + task.updatedAt = new Date(); + + return task; + }, + + deleteMany: async (args: { + where: { + id: string; + userId: string; + }; + }) => { + const beforeCount = this.tasks.length; + this.tasks = this.tasks.filter( + (task) => !(task.id === args.where.id && task.userId === args.where.userId) + ); + this.taskTags = this.taskTags.filter((taskTag) => taskTag.taskId !== args.where.id); + return { + count: beforeCount - this.tasks.length + }; + }, + + findUniqueOrThrow: async (args: { + where: { + id: string; + }; + }) => { + const task = this.tasks.find((item) => item.id === args.where.id); + if (!task) { + throw new Error("task not found"); + } + + return this.toTaskWithTags(task); + } + }; + + readonly tag = { + upsert: async (args: { + where: { + userId_name: { + userId: string; + name: string; + }; + }; + create: { + userId: string; + name: string; + }; + }) => { + const existing = this.tags.find( + (tag) => + tag.userId === args.where.userId_name.userId && tag.name === args.where.userId_name.name + ); + if (existing) { + return existing; + } + + const createdTag: TagRecord = { + id: `tag_${this.tagIdSequence++}`, + userId: args.create.userId, + name: args.create.name + }; + this.tags.push(createdTag); + return createdTag; + } + }; + + readonly taskTag = { + deleteMany: async (args: { + where: { + taskId: string; + }; + }) => { + const beforeCount = this.taskTags.length; + this.taskTags = this.taskTags.filter((taskTag) => taskTag.taskId !== args.where.taskId); + return { + count: beforeCount - this.taskTags.length + }; + }, + + createMany: async (args: { + data: Array<{ + taskId: string; + tagId: string; + }>; + }) => { + for (const row of args.data) { + const existing = this.taskTags.find( + (taskTag) => taskTag.taskId === row.taskId && taskTag.tagId === row.tagId + ); + if (!existing) { + this.taskTags.push(row); + } + } + return { + count: args.data.length + }; + } + }; + + async $transaction(runner: (tx: InMemoryPrismaService) => Promise): Promise { + return runner(this); + } + + private toTaskWithTags( + task: TaskRecord + ): TaskRecord & { taskTags: Array<{ tag: { name: string } }> } { + return { + ...task, + taskTags: this.taskTags + .filter((taskTag) => taskTag.taskId === task.id) + .map((taskTag) => this.tags.find((tag) => tag.id === taskTag.tagId)) + .filter((tag): tag is TagRecord => tag !== undefined) + .map((tag) => ({ + tag: { + name: tag.name + } + })) + }; + } + + private getTaskTagNames(taskId: string): string[] { + return this.taskTags + .filter((taskTag) => taskTag.taskId === taskId) + .map((taskTag) => this.tags.find((tag) => tag.id === taskTag.tagId)) + .filter((tag): tag is TagRecord => tag !== undefined) + .map((tag) => tag.name); + } +} + +describe("TaskController (integration)", () => { + let app: INestApplication; + const prismaService = new InMemoryPrismaService(); + + beforeAll(async () => { + const moduleRef: TestingModule = await Test.createTestingModule({ + controllers: [TaskController], + providers: [ + TaskService, + { provide: PrismaService, useValue: prismaService as unknown as PrismaService } + ] + }).compile(); + + app = moduleRef.createNestApplication(); + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + whitelist: true, + forbidNonWhitelisted: true + }) + ); + + await app.init(); + }); + + afterAll(async () => { + await app.close(); + }); + + it("should create, query, update and delete a task", async () => { + const createResponse = await request(app.getHttpServer()) + .post("/tasks") + .set("x-user-id", "user_1") + .send({ + title: "准备周会", + contentText: "整理本周进度", + priority: "HIGH", + tagNames: ["工作", "会议"] + }) + .expect(201); + + expect(createResponse.body.id).toBeDefined(); + expect(createResponse.body.tags).toEqual(["工作", "会议"]); + const taskId = createResponse.body.id as string; + + const listResponse = await request(app.getHttpServer()) + .get("/tasks") + .set("x-user-id", "user_1") + .query({ tags: "会议" }) + .expect(200); + + expect(listResponse.body.total).toBe(1); + expect(listResponse.body.items[0].id).toBe(taskId); + + const updateResponse = await request(app.getHttpServer()) + .patch(`/tasks/${taskId}`) + .set("x-user-id", "user_1") + .send({ + status: "DONE" + }) + .expect(200); + + expect(updateResponse.body.status).toBe("DONE"); + expect(updateResponse.body.completedAt).toBeTruthy(); + expect(updateResponse.body.version).toBe(2); + + await request(app.getHttpServer()) + .delete(`/tasks/${taskId}`) + .set("x-user-id", "user_1") + .expect(200) + .expect({ + success: true + }); + + const listAfterDeleteResponse = await request(app.getHttpServer()) + .get("/tasks") + .set("x-user-id", "user_1") + .expect(200); + expect(listAfterDeleteResponse.body.total).toBe(0); + }); +}); diff --git a/apps/api/tsconfig.spec.json b/apps/api/tsconfig.spec.json new file mode 100644 index 0000000..e83c972 --- /dev/null +++ b/apps/api/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": ["src/**/*.ts", "generated/prisma/**/*.ts", "test/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4726407..a87e0fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,12 @@ importers: specifier: ^7.8.2 version: 7.8.2 devDependencies: + "@nestjs/testing": + specifier: ^11.1.18 + version: 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(@nestjs/platform-express@11.1.18) + "@types/jest": + specifier: ^30.0.0 + version: 30.0.0 "@types/node": specifier: ^25.5.2 version: 25.5.2 @@ -83,12 +89,24 @@ importers: "@types/passport-oauth2": specifier: ^1.8.0 version: 1.8.0 + "@types/supertest": + specifier: ^7.2.0 + version: 7.2.0 dotenv: specifier: ^16.6.1 version: 16.6.1 + jest: + specifier: ^30.3.0 + version: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)) prisma: specifier: ^7.6.0 version: 7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + supertest: + specifier: ^7.2.2 + version: 7.2.2 + ts-jest: + specifier: ^29.4.9 + version: 29.4.9(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@30.3.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@25.5.2)(typescript@5.9.3) @@ -397,6 +415,276 @@ packages: } engines: { node: ">=18.0.0" } + "@babel/code-frame@7.29.0": + resolution: + { + integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== + } + engines: { node: ">=6.9.0" } + + "@babel/compat-data@7.29.0": + resolution: + { + integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== + } + engines: { node: ">=6.9.0" } + + "@babel/core@7.29.0": + resolution: + { + integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== + } + engines: { node: ">=6.9.0" } + + "@babel/generator@7.29.1": + resolution: + { + integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-compilation-targets@7.28.6": + resolution: + { + integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-globals@7.28.0": + resolution: + { + integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-module-imports@7.28.6": + resolution: + { + integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-module-transforms@7.28.6": + resolution: + { + integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/helper-plugin-utils@7.28.6": + resolution: + { + integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-string-parser@7.27.1": + resolution: + { + integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-validator-identifier@7.28.5": + resolution: + { + integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-validator-option@7.27.1": + resolution: + { + integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + } + engines: { node: ">=6.9.0" } + + "@babel/helpers@7.29.2": + resolution: + { + integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw== + } + engines: { node: ">=6.9.0" } + + "@babel/parser@7.29.2": + resolution: + { + integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA== + } + engines: { node: ">=6.0.0" } + hasBin: true + + "@babel/plugin-syntax-async-generators@7.8.4": + resolution: + { + integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-bigint@7.8.3": + resolution: + { + integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-class-properties@7.12.13": + resolution: + { + integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-class-static-block@7.14.5": + resolution: + { + integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-import-attributes@7.28.6": + resolution: + { + integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-import-meta@7.10.4": + resolution: + { + integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-json-strings@7.8.3": + resolution: + { + integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-jsx@7.28.6": + resolution: + { + integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-logical-assignment-operators@7.10.4": + resolution: + { + integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-nullish-coalescing-operator@7.8.3": + resolution: + { + integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-numeric-separator@7.10.4": + resolution: + { + integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-object-rest-spread@7.8.3": + resolution: + { + integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-optional-catch-binding@7.8.3": + resolution: + { + integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-optional-chaining@7.8.3": + resolution: + { + integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-private-property-in-object@7.14.5": + resolution: + { + integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-top-level-await@7.14.5": + resolution: + { + integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-typescript@7.28.6": + resolution: + { + integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/template@7.28.6": + resolution: + { + integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + } + engines: { node: ">=6.9.0" } + + "@babel/traverse@7.29.0": + resolution: + { + integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== + } + engines: { node: ">=6.9.0" } + + "@babel/types@7.29.0": + resolution: + { + integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + } + engines: { node: ">=6.9.0" } + + "@bcoe/v8-coverage@0.2.3": + resolution: + { + integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + } + "@borewit/text-codec@0.2.2": resolution: { @@ -445,6 +733,24 @@ packages: integrity: sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q== } + "@emnapi/core@1.9.2": + resolution: + { + integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA== + } + + "@emnapi/runtime@1.9.2": + resolution: + { + integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw== + } + + "@emnapi/wasi-threads@1.2.1": + resolution: + { + integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w== + } + "@eslint-community/eslint-utils@4.9.1": resolution: { @@ -545,6 +851,175 @@ packages: } engines: { node: ">=18.18" } + "@isaacs/cliui@8.0.2": + resolution: + { + integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + } + engines: { node: ">=12" } + + "@istanbuljs/load-nyc-config@1.1.0": + resolution: + { + integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + } + engines: { node: ">=8" } + + "@istanbuljs/schema@0.1.3": + resolution: + { + integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + } + engines: { node: ">=8" } + + "@jest/console@30.3.0": + resolution: + { + integrity: sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/core@30.3.0": + resolution: + { + integrity: sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + "@jest/diff-sequences@30.3.0": + resolution: + { + integrity: sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/environment@30.3.0": + resolution: + { + integrity: sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/expect-utils@30.3.0": + resolution: + { + integrity: sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/expect@30.3.0": + resolution: + { + integrity: sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/fake-timers@30.3.0": + resolution: + { + integrity: sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/get-type@30.1.0": + resolution: + { + integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/globals@30.3.0": + resolution: + { + integrity: sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/pattern@30.0.1": + resolution: + { + integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/reporters@30.3.0": + resolution: + { + integrity: sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + "@jest/schemas@30.0.5": + resolution: + { + integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/snapshot-utils@30.3.0": + resolution: + { + integrity: sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/source-map@30.0.1": + resolution: + { + integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/test-result@30.3.0": + resolution: + { + integrity: sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/test-sequencer@30.3.0": + resolution: + { + integrity: sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/transform@30.3.0": + resolution: + { + integrity: sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/types@30.3.0": + resolution: + { + integrity: sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jridgewell/gen-mapping@0.3.13": + resolution: + { + integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + } + + "@jridgewell/remapping@2.3.5": + resolution: + { + integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + } + "@jridgewell/resolve-uri@3.1.2": resolution: { @@ -558,6 +1033,12 @@ packages: integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== } + "@jridgewell/trace-mapping@0.3.31": + resolution: + { + integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + } + "@jridgewell/trace-mapping@0.3.9": resolution: { @@ -577,6 +1058,12 @@ packages: } engines: { node: ">=8" } + "@napi-rs/wasm-runtime@0.2.12": + resolution: + { + integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== + } + "@nestjs/common@11.1.18": resolution: { @@ -640,6 +1127,29 @@ packages: "@nestjs/common": ^11.0.0 "@nestjs/core": ^11.0.0 + "@nestjs/testing@11.1.18": + resolution: + { + integrity: sha512-frzwNlpBgtAzI3hp/qo57DZoRO4RMTH1wST3QUYEhRTHyfPkLpzkWz3jV/mhApXjD0yT56Ptlzn6zuYPLh87Lw== + } + peerDependencies: + "@nestjs/common": ^11.0.0 + "@nestjs/core": ^11.0.0 + "@nestjs/microservices": ^11.0.0 + "@nestjs/platform-express": ^11.0.0 + peerDependenciesMeta: + "@nestjs/microservices": + optional: true + "@nestjs/platform-express": + optional: true + + "@noble/hashes@1.8.0": + resolution: + { + integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== + } + engines: { node: ^14.21.3 || >=16 } + "@noble/hashes@2.0.1": resolution: { @@ -718,6 +1228,26 @@ packages: integrity: sha512-x1ozBa5bPbdZCrrTL/HK21qchiK7jYElTu+0ft22abeEhiLYgH1+SIULvOcVk3CK8YwF4kdcidvkq4ciejucJA== } + "@paralleldrive/cuid2@2.3.1": + resolution: + { + integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw== + } + + "@pkgjs/parseargs@0.11.0": + resolution: + { + integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + } + engines: { node: ">=14" } + + "@pkgr/core@0.2.9": + resolution: + { + integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + "@prisma/client-runtime-utils@7.6.0": resolution: { @@ -921,6 +1451,24 @@ packages: integrity: sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w== } + "@sinclair/typebox@0.34.49": + resolution: + { + integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A== + } + + "@sinonjs/commons@3.0.1": + resolution: + { + integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + } + + "@sinonjs/fake-timers@15.3.0": + resolution: + { + integrity: sha512-m2xozxSfCIxjDdvbhIWazlP2i2aha/iUmbl94alpsIbd3iLTfeXgfBVbwyWogB6l++istyGZqamgA/EcqYf+Bg== + } + "@smithy/chunked-blob-reader-native@4.2.3": resolution: { @@ -1383,6 +1931,36 @@ packages: cpu: [arm64] os: [win32] + "@tybys/wasm-util@0.10.1": + resolution: + { + integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== + } + + "@types/babel__core@7.20.5": + resolution: + { + integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + } + + "@types/babel__generator@7.27.0": + resolution: + { + integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + } + + "@types/babel__template@7.4.4": + resolution: + { + integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + } + + "@types/babel__traverse@7.28.0": + resolution: + { + integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + } + "@types/body-parser@1.19.6": resolution: { @@ -1395,6 +1973,12 @@ packages: integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== } + "@types/cookiejar@2.1.5": + resolution: + { + integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== + } + "@types/esrecurse@4.3.1": resolution: { @@ -1425,6 +2009,30 @@ packages: integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== } + "@types/istanbul-lib-coverage@2.0.6": + resolution: + { + integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + } + + "@types/istanbul-lib-report@3.0.3": + resolution: + { + integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + } + + "@types/istanbul-reports@3.0.4": + resolution: + { + integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + } + + "@types/jest@30.0.0": + resolution: + { + integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + } + "@types/json-schema@7.0.15": resolution: { @@ -1437,6 +2045,12 @@ packages: integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA== } + "@types/methods@1.1.4": + resolution: + { + integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== + } + "@types/ms@2.1.0": resolution: { @@ -1503,6 +2117,12 @@ packages: integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== } + "@types/stack-utils@2.0.3": + resolution: + { + integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + } + "@types/strip-bom@3.0.0": resolution: { @@ -1515,12 +2135,194 @@ packages: integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== } + "@types/superagent@8.1.9": + resolution: + { + integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ== + } + + "@types/supertest@7.2.0": + resolution: + { + integrity: sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw== + } + "@types/validator@13.15.10": resolution: { integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA== } + "@types/yargs-parser@21.0.3": + resolution: + { + integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + } + + "@types/yargs@17.0.35": + resolution: + { + integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== + } + + "@ungap/structured-clone@1.3.0": + resolution: + { + integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + } + + "@unrs/resolver-binding-android-arm-eabi@1.11.1": + resolution: + { + integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw== + } + cpu: [arm] + os: [android] + + "@unrs/resolver-binding-android-arm64@1.11.1": + resolution: + { + integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g== + } + cpu: [arm64] + os: [android] + + "@unrs/resolver-binding-darwin-arm64@1.11.1": + resolution: + { + integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g== + } + cpu: [arm64] + os: [darwin] + + "@unrs/resolver-binding-darwin-x64@1.11.1": + resolution: + { + integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ== + } + cpu: [x64] + os: [darwin] + + "@unrs/resolver-binding-freebsd-x64@1.11.1": + resolution: + { + integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw== + } + cpu: [x64] + os: [freebsd] + + "@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": + resolution: + { + integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw== + } + cpu: [arm] + os: [linux] + + "@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": + resolution: + { + integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw== + } + cpu: [arm] + os: [linux] + + "@unrs/resolver-binding-linux-arm64-gnu@1.11.1": + resolution: + { + integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ== + } + cpu: [arm64] + os: [linux] + + "@unrs/resolver-binding-linux-arm64-musl@1.11.1": + resolution: + { + integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w== + } + cpu: [arm64] + os: [linux] + + "@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": + resolution: + { + integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA== + } + cpu: [ppc64] + os: [linux] + + "@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": + resolution: + { + integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ== + } + cpu: [riscv64] + os: [linux] + + "@unrs/resolver-binding-linux-riscv64-musl@1.11.1": + resolution: + { + integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew== + } + cpu: [riscv64] + os: [linux] + + "@unrs/resolver-binding-linux-s390x-gnu@1.11.1": + resolution: + { + integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg== + } + cpu: [s390x] + os: [linux] + + "@unrs/resolver-binding-linux-x64-gnu@1.11.1": + resolution: + { + integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w== + } + cpu: [x64] + os: [linux] + + "@unrs/resolver-binding-linux-x64-musl@1.11.1": + resolution: + { + integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA== + } + cpu: [x64] + os: [linux] + + "@unrs/resolver-binding-wasm32-wasi@1.11.1": + resolution: + { + integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ== + } + engines: { node: ">=14.0.0" } + cpu: [wasm32] + + "@unrs/resolver-binding-win32-arm64-msvc@1.11.1": + resolution: + { + integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw== + } + cpu: [arm64] + os: [win32] + + "@unrs/resolver-binding-win32-ia32-msvc@1.11.1": + resolution: + { + integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ== + } + cpu: [ia32] + os: [win32] + + "@unrs/resolver-binding-win32-x64-msvc@1.11.1": + resolution: + { + integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== + } + cpu: [x64] + os: [win32] + accepts@2.0.0: resolution: { @@ -1563,6 +2365,13 @@ packages: integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== } + ansi-escapes@4.3.2: + resolution: + { + integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + } + engines: { node: ">=8" } + ansi-escapes@7.3.0: resolution: { @@ -1570,6 +2379,13 @@ packages: } engines: { node: ">=18" } + ansi-regex@5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + } + engines: { node: ">=8" } + ansi-regex@6.2.2: resolution: { @@ -1577,6 +2393,20 @@ packages: } engines: { node: ">=12" } + ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + } + engines: { node: ">=8" } + + ansi-styles@5.2.0: + resolution: + { + integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + } + engines: { node: ">=10" } + ansi-styles@6.2.3: resolution: { @@ -1603,6 +2433,24 @@ packages: integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== } + argparse@1.0.10: + resolution: + { + integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + } + + asap@2.0.6: + resolution: + { + integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + } + + asynckit@0.4.0: + resolution: + { + integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + } + aws-ssl-profiles@1.1.2: resolution: { @@ -1610,6 +2458,46 @@ packages: } engines: { node: ">= 6.0.0" } + babel-jest@30.3.0: + resolution: + { + integrity: sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + peerDependencies: + "@babel/core": ^7.11.0 || ^8.0.0-0 + + babel-plugin-istanbul@7.0.1: + resolution: + { + integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA== + } + engines: { node: ">=12" } + + babel-plugin-jest-hoist@30.3.0: + resolution: + { + integrity: sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + babel-preset-current-node-syntax@1.2.0: + resolution: + { + integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== + } + peerDependencies: + "@babel/core": ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@30.3.0: + resolution: + { + integrity: sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + peerDependencies: + "@babel/core": ^7.11.0 || ^8.0.0-beta.1 + balanced-match@1.0.2: resolution: { @@ -1623,6 +2511,14 @@ packages: } engines: { node: 18 || 20 || >=22 } + baseline-browser-mapping@2.10.14: + resolution: + { + integrity: sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA== + } + engines: { node: ">=6.0.0" } + hasBin: true + better-result@2.7.0: resolution: { @@ -1656,6 +2552,12 @@ packages: integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w== } + brace-expansion@2.0.3: + resolution: + { + integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA== + } + brace-expansion@5.0.5: resolution: { @@ -1670,6 +2572,27 @@ packages: } engines: { node: ">=8" } + browserslist@4.28.2: + resolution: + { + integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg== + } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + + bs-logger@0.2.6: + resolution: + { + integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + } + engines: { node: ">= 6" } + + bser@2.1.1: + resolution: + { + integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + } + buffer-equal-constant-time@1.0.1: resolution: { @@ -1721,6 +2644,47 @@ packages: } engines: { node: ">= 0.4" } + callsites@3.1.0: + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + } + engines: { node: ">=6" } + + camelcase@5.3.1: + resolution: + { + integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + } + engines: { node: ">=6" } + + camelcase@6.3.0: + resolution: + { + integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + } + engines: { node: ">=10" } + + caniuse-lite@1.0.30001785: + resolution: + { + integrity: sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ== + } + + chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + } + engines: { node: ">=10" } + + char-regex@1.0.2: + resolution: + { + integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + } + engines: { node: ">=10" } + chart.js@4.5.1: resolution: { @@ -1742,6 +2706,13 @@ packages: } engines: { node: ">= 14.16.0" } + ci-info@4.4.0: + resolution: + { + integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== + } + engines: { node: ">=8" } + citty@0.1.6: resolution: { @@ -1754,6 +2725,12 @@ packages: integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w== } + cjs-module-lexer@2.2.0: + resolution: + { + integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ== + } + class-transformer@0.5.1: resolution: { @@ -1780,12 +2757,52 @@ packages: } engines: { node: ">=20" } + cliui@8.0.1: + resolution: + { + integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + } + engines: { node: ">=12" } + + co@4.6.0: + resolution: + { + integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + } + engines: { iojs: ">= 1.0.0", node: ">= 0.12.0" } + + collect-v8-coverage@1.0.3: + resolution: + { + integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw== + } + + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + } + engines: { node: ">=7.0.0" } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + } + colorette@2.0.20: resolution: { integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== } + combined-stream@1.0.8: + resolution: + { + integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + } + engines: { node: ">= 0.8" } + commander@14.0.3: resolution: { @@ -1793,6 +2810,12 @@ packages: } engines: { node: ">=20" } + component-emitter@1.3.1: + resolution: + { + integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== + } + concat-map@0.0.1: resolution: { @@ -1833,6 +2856,12 @@ packages: } engines: { node: ">= 0.6" } + convert-source-map@2.0.0: + resolution: + { + integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + } + cookie-signature@1.2.2: resolution: { @@ -1847,6 +2876,12 @@ packages: } engines: { node: ">= 0.6" } + cookiejar@2.1.4: + resolution: + { + integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== + } + cors@2.8.6: resolution: { @@ -1885,6 +2920,17 @@ packages: supports-color: optional: true + dedent@1.7.2: + resolution: + { + integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA== + } + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-is@0.1.4: resolution: { @@ -1898,12 +2944,26 @@ packages: } engines: { node: ">=16.0.0" } + deepmerge@4.3.1: + resolution: + { + integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + } + engines: { node: ">=0.10.0" } + defu@6.1.6: resolution: { integrity: sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug== } + delayed-stream@1.0.0: + resolution: + { + integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + } + engines: { node: ">=0.4.0" } + denque@2.1.0: resolution: { @@ -1924,6 +2984,19 @@ packages: integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA== } + detect-newline@3.1.0: + resolution: + { + integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + } + engines: { node: ">=8" } + + dezalgo@1.0.4: + resolution: + { + integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== + } + diff@4.0.4: resolution: { @@ -1965,6 +3038,12 @@ packages: integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ== } + eastasianwidth@0.2.0: + resolution: + { + integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + } + ecdsa-sig-formatter@1.0.11: resolution: { @@ -1983,12 +3062,37 @@ packages: integrity: sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw== } + electron-to-chromium@1.5.331: + resolution: + { + integrity: sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q== + } + + emittery@0.13.1: + resolution: + { + integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + } + engines: { node: ">=12" } + emoji-regex@10.6.0: resolution: { integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A== } + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + } + + emoji-regex@9.2.2: + resolution: + { + integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + } + empathic@2.0.0: resolution: { @@ -2017,6 +3121,12 @@ packages: } engines: { node: ">=18" } + error-ex@1.3.4: + resolution: + { + integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== + } + es-define-property@1.0.1: resolution: { @@ -2038,12 +3148,33 @@ packages: } engines: { node: ">= 0.4" } + es-set-tostringtag@2.1.0: + resolution: + { + integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + } + engines: { node: ">= 0.4" } + + escalade@3.2.0: + resolution: + { + integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + } + engines: { node: ">=6" } + escape-html@1.0.3: resolution: { integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== } + escape-string-regexp@2.0.0: + resolution: + { + integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + } + engines: { node: ">=8" } + escape-string-regexp@4.0.0: resolution: { @@ -2092,6 +3223,14 @@ packages: } engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + esprima@4.0.1: + resolution: + { + integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + } + engines: { node: ">=4" } + hasBin: true + esquery@1.7.0: resolution: { @@ -2133,6 +3272,27 @@ packages: integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== } + execa@5.1.1: + resolution: + { + integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + } + engines: { node: ">=10" } + + exit-x@0.2.2: + resolution: + { + integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ== + } + engines: { node: ">= 0.8.0" } + + expect@30.3.0: + resolution: + { + integrity: sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + express@5.2.1: resolution: { @@ -2196,6 +3356,12 @@ packages: } hasBin: true + fb-watchman@2.0.2: + resolution: + { + integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + } + file-entry-cache@8.0.0: resolution: { @@ -2224,6 +3390,13 @@ packages: } engines: { node: ">= 18.0.0" } + find-up@4.1.0: + resolution: + { + integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + } + engines: { node: ">=8" } + find-up@5.0.0: resolution: { @@ -2251,6 +3424,20 @@ packages: } engines: { node: ">=14" } + form-data@4.0.5: + resolution: + { + integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== + } + engines: { node: ">= 6" } + + formidable@3.5.4: + resolution: + { + integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug== + } + engines: { node: ">=14.0.0" } + forwarded@0.2.0: resolution: { @@ -2291,6 +3478,20 @@ packages: integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== } + gensync@1.0.0-beta.2: + resolution: + { + integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + } + engines: { node: ">=6.9.0" } + + get-caller-file@2.0.5: + resolution: + { + integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + } + engines: { node: 6.* || 8.* || >= 10.* } + get-east-asian-width@1.5.0: resolution: { @@ -2305,6 +3506,13 @@ packages: } engines: { node: ">= 0.4" } + get-package-type@0.1.0: + resolution: + { + integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + } + engines: { node: ">=8.0.0" } + get-port-please@3.2.0: resolution: { @@ -2318,6 +3526,13 @@ packages: } engines: { node: ">= 0.4" } + get-stream@6.0.1: + resolution: + { + integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + } + engines: { node: ">=10" } + giget@2.0.0: resolution: { @@ -2339,6 +3554,14 @@ packages: } engines: { node: ">=10.13.0" } + glob@10.5.0: + resolution: + { + integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== + } + 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 + hasBin: true + glob@7.2.3: resolution: { @@ -2378,6 +3601,21 @@ packages: integrity: sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg== } + handlebars@4.7.9: + resolution: + { + integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ== + } + engines: { node: ">=0.4.7" } + hasBin: true + + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + } + engines: { node: ">=8" } + has-symbols@1.1.0: resolution: { @@ -2385,6 +3623,13 @@ packages: } engines: { node: ">= 0.4" } + has-tostringtag@1.0.2: + resolution: + { + integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + } + engines: { node: ">= 0.4" } + hasown@2.0.2: resolution: { @@ -2399,6 +3644,12 @@ packages: } engines: { node: ">=16.9.0" } + html-escaper@2.0.2: + resolution: + { + integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + } + http-errors@2.0.1: resolution: { @@ -2412,6 +3663,13 @@ packages: integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA== } + human-signals@2.1.0: + resolution: + { + integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + } + engines: { node: ">=10.17.0" } + husky@9.1.7: resolution: { @@ -2440,6 +3698,14 @@ packages: } engines: { node: ">= 4" } + import-local@3.2.0: + resolution: + { + integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + } + engines: { node: ">=8" } + hasBin: true + imurmurhash@0.1.4: resolution: { @@ -2467,6 +3733,12 @@ packages: } engines: { node: ">= 0.10" } + is-arrayish@0.2.1: + resolution: + { + integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + } + is-binary-path@2.1.0: resolution: { @@ -2488,6 +3760,13 @@ packages: } engines: { node: ">=0.10.0" } + is-fullwidth-code-point@3.0.0: + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + } + engines: { node: ">=8" } + is-fullwidth-code-point@5.1.0: resolution: { @@ -2495,6 +3774,13 @@ packages: } engines: { node: ">=18" } + is-generator-fn@2.1.0: + resolution: + { + integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + } + engines: { node: ">=6" } + is-glob@4.0.3: resolution: { @@ -2521,12 +3807,54 @@ packages: integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== } + is-stream@2.0.1: + resolution: + { + integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + } + engines: { node: ">=8" } + isexe@2.0.0: resolution: { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } + istanbul-lib-coverage@3.2.2: + resolution: + { + integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + } + engines: { node: ">=8" } + + istanbul-lib-instrument@6.0.3: + resolution: + { + integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + } + engines: { node: ">=10" } + + istanbul-lib-report@3.0.1: + resolution: + { + integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + } + engines: { node: ">=10" } + + istanbul-lib-source-maps@5.0.6: + resolution: + { + integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A== + } + engines: { node: ">=10" } + + istanbul-reports@3.2.0: + resolution: + { + integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + } + engines: { node: ">=8" } + iterare@1.2.1: resolution: { @@ -2534,6 +3862,215 @@ packages: } engines: { node: ">=6" } + jackspeak@3.4.3: + resolution: + { + integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + } + + jest-changed-files@30.3.0: + resolution: + { + integrity: sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-circus@30.3.0: + resolution: + { + integrity: sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-cli@30.3.0: + resolution: + { + integrity: sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@30.3.0: + resolution: + { + integrity: sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + peerDependencies: + "@types/node": "*" + esbuild-register: ">=3.4.0" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + esbuild-register: + optional: true + ts-node: + optional: true + + jest-diff@30.3.0: + resolution: + { + integrity: sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-docblock@30.2.0: + resolution: + { + integrity: sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-each@30.3.0: + resolution: + { + integrity: sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-environment-node@30.3.0: + resolution: + { + integrity: sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-haste-map@30.3.0: + resolution: + { + integrity: sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-leak-detector@30.3.0: + resolution: + { + integrity: sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-matcher-utils@30.3.0: + resolution: + { + integrity: sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-message-util@30.3.0: + resolution: + { + integrity: sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-mock@30.3.0: + resolution: + { + integrity: sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-pnp-resolver@1.2.3: + resolution: + { + integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + } + engines: { node: ">=6" } + peerDependencies: + jest-resolve: "*" + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@30.0.1: + resolution: + { + integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-resolve-dependencies@30.3.0: + resolution: + { + integrity: sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-resolve@30.3.0: + resolution: + { + integrity: sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-runner@30.3.0: + resolution: + { + integrity: sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-runtime@30.3.0: + resolution: + { + integrity: sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-snapshot@30.3.0: + resolution: + { + integrity: sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-util@30.3.0: + resolution: + { + integrity: sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-validate@30.3.0: + resolution: + { + integrity: sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-watcher@30.3.0: + resolution: + { + integrity: sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-worker@30.3.0: + resolution: + { + integrity: sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest@30.3.0: + resolution: + { + integrity: sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + jiti@2.6.1: resolution: { @@ -2541,12 +4078,39 @@ packages: } hasBin: true + js-tokens@4.0.0: + resolution: + { + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + } + + js-yaml@3.14.2: + resolution: + { + integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== + } + hasBin: true + + jsesc@3.1.0: + resolution: + { + integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + } + engines: { node: ">=6" } + hasBin: true + json-buffer@3.0.1: resolution: { integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== } + json-parse-even-better-errors@2.3.1: + resolution: + { + integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + } + json-schema-traverse@0.4.1: resolution: { @@ -2565,6 +4129,14 @@ packages: integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== } + json5@2.2.3: + resolution: + { + integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + } + engines: { node: ">=6" } + hasBin: true + jsonwebtoken@9.0.3: resolution: { @@ -2590,6 +4162,13 @@ packages: integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== } + leven@3.1.0: + resolution: + { + integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + } + engines: { node: ">=6" } + levn@0.4.1: resolution: { @@ -2603,6 +4182,12 @@ packages: integrity: sha512-lsmMmGXBxXIK/VMLEj0kL6MtUs1kBGj1nTCzi6zgQoG1DEwqwt2DQyHxcLykceIxAnfE3hya7NuIh6PpC6S3fA== } + lines-and-columns@1.2.4: + resolution: + { + integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + } + lint-staged@16.4.0: resolution: { @@ -2625,6 +4210,13 @@ packages: } engines: { node: ">=13.2.0" } + locate-path@5.0.0: + resolution: + { + integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + } + engines: { node: ">=8" } + locate-path@6.0.0: resolution: { @@ -2668,6 +4260,12 @@ packages: integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== } + lodash.memoize@4.1.2: + resolution: + { + integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + } + lodash.once@4.1.1: resolution: { @@ -2693,6 +4291,18 @@ packages: integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== } + lru-cache@10.4.3: + resolution: + { + integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + } + + lru-cache@5.1.1: + resolution: + { + integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + } + lru.min@1.1.4: resolution: { @@ -2700,12 +4310,25 @@ packages: } engines: { bun: ">=1.0.0", deno: ">=1.30.0", node: ">=8.0.0" } + make-dir@4.0.0: + resolution: + { + integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + } + engines: { node: ">=10" } + make-error@1.3.6: resolution: { integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== } + makeerror@1.0.12: + resolution: + { + integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + } + math-intrinsics@1.1.0: resolution: { @@ -2734,6 +4357,19 @@ packages: } engines: { node: ">=18" } + merge-stream@2.0.0: + resolution: + { + integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + } + + methods@1.1.2: + resolution: + { + integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + } + engines: { node: ">= 0.6" } + mime-db@1.52.0: resolution: { @@ -2762,6 +4398,21 @@ packages: } engines: { node: ">=18" } + mime@2.6.0: + resolution: + { + integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + } + engines: { node: ">=4.0.0" } + hasBin: true + + mimic-fn@2.1.0: + resolution: + { + integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + } + engines: { node: ">=6" } + mimic-function@5.0.1: resolution: { @@ -2782,12 +4433,26 @@ packages: integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== } + minimatch@9.0.9: + resolution: + { + integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== + } + engines: { node: ">=16 || 14 >=14.17" } + minimist@1.2.8: resolution: { integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== } + minipass@7.1.3: + resolution: + { + integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== + } + engines: { node: ">=16 || 14 >=14.17" } + mkdirp@1.0.4: resolution: { @@ -2823,6 +4488,14 @@ packages: } engines: { node: ">=8.0.0" } + napi-postinstall@0.3.4: + resolution: + { + integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ== + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + hasBin: true + natural-compare@1.4.0: resolution: { @@ -2836,12 +4509,30 @@ packages: } engines: { node: ">= 0.6" } + neo-async@2.6.2: + resolution: + { + integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + } + node-fetch-native@1.6.7: resolution: { integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q== } + node-int64@0.4.0: + resolution: + { + integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + } + + node-releases@2.0.37: + resolution: + { + integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg== + } + normalize-path@3.0.0: resolution: { @@ -2849,6 +4540,13 @@ packages: } engines: { node: ">=0.10.0" } + npm-run-path@4.0.1: + resolution: + { + integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + } + engines: { node: ">=8" } + nypm@0.6.5: resolution: { @@ -2890,6 +4588,13 @@ packages: integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== } + onetime@5.1.2: + resolution: + { + integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + } + engines: { node: ">=6" } + onetime@7.0.0: resolution: { @@ -2910,6 +4615,13 @@ packages: integrity: sha512-RUcYcRMCgRWhUE/XabRppXpUwCwaWBNHe5iPXhdvP8wwDGpGpsIf/kxX/ec3zFsOaM1Oq8lEhUqDwk6W7DHkwg== } + p-limit@2.3.0: + resolution: + { + integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + } + engines: { node: ">=6" } + p-limit@3.1.0: resolution: { @@ -2917,6 +4629,13 @@ packages: } engines: { node: ">=10" } + p-locate@4.1.0: + resolution: + { + integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + } + engines: { node: ">=8" } + p-locate@5.0.0: resolution: { @@ -2924,6 +4643,26 @@ packages: } engines: { node: ">=10" } + p-try@2.2.0: + resolution: + { + integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + } + engines: { node: ">=6" } + + package-json-from-dist@1.0.1: + resolution: + { + integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + } + + parse-json@5.2.0: + resolution: + { + integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + } + engines: { node: ">=8" } + parseurl@1.3.3: resolution: { @@ -2965,6 +4704,13 @@ packages: integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== } + path-scurry@1.11.1: + resolution: + { + integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + } + engines: { node: ">=16 || 14 >=14.18" } + path-to-regexp@8.4.2: resolution: { @@ -3003,6 +4749,20 @@ packages: } engines: { node: ">=12" } + pirates@4.0.7: + resolution: + { + integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + } + engines: { node: ">= 6" } + + pkg-dir@4.2.0: + resolution: + { + integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + } + engines: { node: ">=8" } + pkg-types@2.3.0: resolution: { @@ -3031,6 +4791,13 @@ packages: engines: { node: ">=14" } hasBin: true + pretty-format@30.3.0: + resolution: + { + integrity: sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ== + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + prisma@7.6.0: resolution: { @@ -3073,6 +4840,12 @@ packages: integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== } + pure-rand@7.0.1: + resolution: + { + integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ== + } + qs@6.15.0: resolution: { @@ -3108,6 +4881,12 @@ packages: peerDependencies: react: ^19.2.4 + react-is@18.3.1: + resolution: + { + integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + } + react@19.2.4: resolution: { @@ -3148,6 +4927,13 @@ packages: integrity: sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ== } + require-directory@2.1.1: + resolution: + { + integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + } + engines: { node: ">=0.10.0" } + require-from-string@2.0.2: resolution: { @@ -3155,6 +4941,20 @@ packages: } engines: { node: ">=0.10.0" } + resolve-cwd@3.0.0: + resolution: + { + integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + } + engines: { node: ">=8" } + + resolve-from@5.0.0: + resolution: + { + integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + } + engines: { node: ">=8" } + resolve@1.22.11: resolution: { @@ -3222,6 +5022,13 @@ packages: integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== } + semver@6.3.1: + resolution: + { + integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + } + hasBin: true + semver@7.7.4: resolution: { @@ -3317,6 +5124,13 @@ packages: integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== } + slash@3.0.0: + resolution: + { + integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + } + engines: { node: ">=8" } + slice-ansi@7.1.2: resolution: { @@ -3331,6 +5145,12 @@ packages: } engines: { node: ">=20" } + source-map-support@0.5.13: + resolution: + { + integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + } + source-map-support@0.5.21: resolution: { @@ -3344,6 +5164,12 @@ packages: } engines: { node: ">=0.10.0" } + sprintf-js@1.0.3: + resolution: + { + integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + } + sqlstring@2.3.3: resolution: { @@ -3351,6 +5177,13 @@ packages: } engines: { node: ">= 0.6" } + stack-utils@2.0.6: + resolution: + { + integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + } + engines: { node: ">=10" } + statuses@2.0.2: resolution: { @@ -3378,6 +5211,27 @@ packages: } engines: { node: ">=0.6.19" } + string-length@4.0.2: + resolution: + { + integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + } + engines: { node: ">=10" } + + string-width@4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + } + engines: { node: ">=8" } + + string-width@5.1.2: + resolution: + { + integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + } + engines: { node: ">=12" } + string-width@7.2.0: resolution: { @@ -3398,6 +5252,13 @@ packages: integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== } + strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + } + engines: { node: ">=8" } + strip-ansi@7.2.0: resolution: { @@ -3412,6 +5273,20 @@ packages: } engines: { node: ">=4" } + strip-bom@4.0.0: + resolution: + { + integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + } + engines: { node: ">=8" } + + strip-final-newline@2.0.0: + resolution: + { + integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + } + engines: { node: ">=6" } + strip-json-comments@2.0.1: resolution: { @@ -3419,6 +5294,13 @@ packages: } engines: { node: ">=0.10.0" } + strip-json-comments@3.1.1: + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + } + engines: { node: ">=8" } + strnum@2.2.2: resolution: { @@ -3432,6 +5314,34 @@ packages: } engines: { node: ">=18" } + superagent@10.3.0: + resolution: + { + integrity: sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ== + } + engines: { node: ">=14.18.0" } + + supertest@7.2.2: + resolution: + { + integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA== + } + engines: { node: ">=14.18.0" } + + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + } + engines: { node: ">=8" } + + supports-color@8.1.1: + resolution: + { + integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + } + engines: { node: ">=10" } + supports-preserve-symlinks-flag@1.0.0: resolution: { @@ -3439,6 +5349,20 @@ packages: } engines: { node: ">= 0.4" } + synckit@0.11.12: + resolution: + { + integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ== + } + engines: { node: ^14.18.0 || >=16.0.0 } + + test-exclude@6.0.0: + resolution: + { + integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + } + engines: { node: ">=8" } + thirty-two@1.0.2: resolution: { @@ -3453,6 +5377,12 @@ packages: } engines: { node: ">=18" } + tmpl@1.0.5: + resolution: + { + integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + } + to-regex-range@5.0.1: resolution: { @@ -3481,6 +5411,36 @@ packages: } hasBin: true + ts-jest@29.4.9: + resolution: + { + integrity: sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ== + } + engines: { node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0 } + hasBin: true + peerDependencies: + "@babel/core": ">=7.0.0-beta.0 <8" + "@jest/transform": ^29.0.0 || ^30.0.0 + "@jest/types": ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: "*" + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: ">=4.3 <7" + peerDependenciesMeta: + "@babel/core": + optional: true + "@jest/transform": + optional: true + "@jest/types": + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + ts-node-dev@2.0.0: resolution: { @@ -3538,6 +5498,27 @@ packages: } engines: { node: ">= 0.8.0" } + type-detect@4.0.8: + resolution: + { + integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + } + engines: { node: ">=4" } + + type-fest@0.21.3: + resolution: + { + integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + } + engines: { node: ">=10" } + + type-fest@4.41.0: + resolution: + { + integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + } + engines: { node: ">=16" } + type-is@1.6.18: resolution: { @@ -3566,6 +5547,14 @@ packages: engines: { node: ">=14.17" } hasBin: true + uglify-js@3.19.3: + resolution: + { + integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + } + engines: { node: ">=0.8.0" } + hasBin: true + uid@2.0.2: resolution: { @@ -3593,6 +5582,21 @@ packages: } engines: { node: ">= 0.8" } + unrs-resolver@1.11.1: + resolution: + { + integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg== + } + + update-browserslist-db@1.2.3: + resolution: + { + integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + } + hasBin: true + peerDependencies: + browserslist: ">= 4.21.0" + uri-js@4.4.1: resolution: { @@ -3611,6 +5615,13 @@ packages: integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== } + v8-to-istanbul@9.3.0: + resolution: + { + integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + } + engines: { node: ">=10.12.0" } + valibot@1.2.0: resolution: { @@ -3636,6 +5647,12 @@ packages: } engines: { node: ">= 0.8" } + walker@1.0.8: + resolution: + { + integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + } + which@2.0.2: resolution: { @@ -3651,6 +5668,26 @@ packages: } engines: { node: ">=0.10.0" } + wordwrap@1.0.0: + resolution: + { + integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + } + + wrap-ansi@7.0.0: + resolution: + { + integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + } + engines: { node: ">=10" } + + wrap-ansi@8.1.0: + resolution: + { + integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + } + engines: { node: ">=12" } + wrap-ansi@9.0.2: resolution: { @@ -3664,6 +5701,13 @@ packages: integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== } + write-file-atomic@5.0.1: + resolution: + { + integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + xtend@4.0.2: resolution: { @@ -3671,6 +5715,19 @@ packages: } engines: { node: ">=0.4" } + y18n@5.0.8: + resolution: + { + integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + } + engines: { node: ">=10" } + + yallist@3.1.1: + resolution: + { + integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + } + yaml@2.8.3: resolution: { @@ -3679,6 +5736,20 @@ packages: engines: { node: ">= 14.6" } hasBin: true + yargs-parser@21.1.1: + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + } + engines: { node: ">=12" } + + yargs@17.7.2: + resolution: + { + integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + } + engines: { node: ">=12" } + yn@3.1.1: resolution: { @@ -4162,6 +6233,195 @@ snapshots: "@aws/lambda-invoke-store@0.2.4": {} + "@babel/code-frame@7.29.0": + dependencies: + "@babel/helper-validator-identifier": 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + "@babel/compat-data@7.29.0": {} + + "@babel/core@7.29.0": + dependencies: + "@babel/code-frame": 7.29.0 + "@babel/generator": 7.29.1 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helpers": 7.29.2 + "@babel/parser": 7.29.2 + "@babel/template": 7.28.6 + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 + "@jridgewell/remapping": 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + "@babel/generator@7.29.1": + dependencies: + "@babel/parser": 7.29.2 + "@babel/types": 7.29.0 + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.31 + jsesc: 3.1.0 + + "@babel/helper-compilation-targets@7.28.6": + dependencies: + "@babel/compat-data": 7.29.0 + "@babel/helper-validator-option": 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + "@babel/helper-globals@7.28.0": {} + + "@babel/helper-module-imports@7.28.6": + dependencies: + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-module-imports": 7.28.6 + "@babel/helper-validator-identifier": 7.28.5 + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-plugin-utils@7.28.6": {} + + "@babel/helper-string-parser@7.27.1": {} + + "@babel/helper-validator-identifier@7.28.5": {} + + "@babel/helper-validator-option@7.27.1": {} + + "@babel/helpers@7.29.2": + dependencies: + "@babel/template": 7.28.6 + "@babel/types": 7.29.0 + + "@babel/parser@7.29.2": + dependencies: + "@babel/types": 7.29.0 + + "@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/template@7.28.6": + dependencies: + "@babel/code-frame": 7.29.0 + "@babel/parser": 7.29.2 + "@babel/types": 7.29.0 + + "@babel/traverse@7.29.0": + dependencies: + "@babel/code-frame": 7.29.0 + "@babel/generator": 7.29.1 + "@babel/helper-globals": 7.28.0 + "@babel/parser": 7.29.2 + "@babel/template": 7.28.6 + "@babel/types": 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + "@babel/types@7.29.0": + dependencies: + "@babel/helper-string-parser": 7.27.1 + "@babel/helper-validator-identifier": 7.28.5 + + "@bcoe/v8-coverage@0.2.3": {} + "@borewit/text-codec@0.2.2": {} "@clack/core@0.5.0": @@ -4189,6 +6449,22 @@ snapshots: "@electric-sql/pglite@0.4.1": {} + "@emnapi/core@1.9.2": + dependencies: + "@emnapi/wasi-threads": 1.2.1 + tslib: 2.8.1 + optional: true + + "@emnapi/runtime@1.9.2": + dependencies: + tslib: 2.8.1 + optional: true + + "@emnapi/wasi-threads@1.2.1": + dependencies: + tslib: 2.8.1 + optional: true + "@eslint-community/eslint-utils@4.9.1(eslint@10.2.0(jiti@2.6.1))": dependencies: eslint: 10.2.0(jiti@2.6.1) @@ -4238,10 +6514,221 @@ snapshots: "@humanwhocodes/retry@0.4.3": {} + "@isaacs/cliui@8.0.2": + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + "@istanbuljs/load-nyc-config@1.1.0": + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.2 + resolve-from: 5.0.0 + + "@istanbuljs/schema@0.1.3": {} + + "@jest/console@30.3.0": + dependencies: + "@jest/types": 30.3.0 + "@types/node": 25.5.2 + chalk: 4.1.2 + jest-message-util: 30.3.0 + jest-util: 30.3.0 + slash: 3.0.0 + + "@jest/core@30.3.0(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3))": + dependencies: + "@jest/console": 30.3.0 + "@jest/pattern": 30.0.1 + "@jest/reporters": 30.3.0 + "@jest/test-result": 30.3.0 + "@jest/transform": 30.3.0 + "@jest/types": 30.3.0 + "@types/node": 25.5.2 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 4.4.0 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-changed-files: 30.3.0 + jest-config: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)) + jest-haste-map: 30.3.0 + jest-message-util: 30.3.0 + jest-regex-util: 30.0.1 + jest-resolve: 30.3.0 + jest-resolve-dependencies: 30.3.0 + jest-runner: 30.3.0 + jest-runtime: 30.3.0 + jest-snapshot: 30.3.0 + jest-util: 30.3.0 + jest-validate: 30.3.0 + jest-watcher: 30.3.0 + pretty-format: 30.3.0 + slash: 3.0.0 + transitivePeerDependencies: + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + "@jest/diff-sequences@30.3.0": {} + + "@jest/environment@30.3.0": + dependencies: + "@jest/fake-timers": 30.3.0 + "@jest/types": 30.3.0 + "@types/node": 25.5.2 + jest-mock: 30.3.0 + + "@jest/expect-utils@30.3.0": + dependencies: + "@jest/get-type": 30.1.0 + + "@jest/expect@30.3.0": + dependencies: + expect: 30.3.0 + jest-snapshot: 30.3.0 + transitivePeerDependencies: + - supports-color + + "@jest/fake-timers@30.3.0": + dependencies: + "@jest/types": 30.3.0 + "@sinonjs/fake-timers": 15.3.0 + "@types/node": 25.5.2 + jest-message-util: 30.3.0 + jest-mock: 30.3.0 + jest-util: 30.3.0 + + "@jest/get-type@30.1.0": {} + + "@jest/globals@30.3.0": + dependencies: + "@jest/environment": 30.3.0 + "@jest/expect": 30.3.0 + "@jest/types": 30.3.0 + jest-mock: 30.3.0 + transitivePeerDependencies: + - supports-color + + "@jest/pattern@30.0.1": + dependencies: + "@types/node": 25.5.2 + jest-regex-util: 30.0.1 + + "@jest/reporters@30.3.0": + dependencies: + "@bcoe/v8-coverage": 0.2.3 + "@jest/console": 30.3.0 + "@jest/test-result": 30.3.0 + "@jest/transform": 30.3.0 + "@jest/types": 30.3.0 + "@jridgewell/trace-mapping": 0.3.31 + "@types/node": 25.5.2 + chalk: 4.1.2 + collect-v8-coverage: 1.0.3 + exit-x: 0.2.2 + glob: 10.5.0 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + jest-message-util: 30.3.0 + jest-util: 30.3.0 + jest-worker: 30.3.0 + slash: 3.0.0 + string-length: 4.0.2 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + "@jest/schemas@30.0.5": + dependencies: + "@sinclair/typebox": 0.34.49 + + "@jest/snapshot-utils@30.3.0": + dependencies: + "@jest/types": 30.3.0 + chalk: 4.1.2 + graceful-fs: 4.2.11 + natural-compare: 1.4.0 + + "@jest/source-map@30.0.1": + dependencies: + "@jridgewell/trace-mapping": 0.3.31 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + "@jest/test-result@30.3.0": + dependencies: + "@jest/console": 30.3.0 + "@jest/types": 30.3.0 + "@types/istanbul-lib-coverage": 2.0.6 + collect-v8-coverage: 1.0.3 + + "@jest/test-sequencer@30.3.0": + dependencies: + "@jest/test-result": 30.3.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.3.0 + slash: 3.0.0 + + "@jest/transform@30.3.0": + dependencies: + "@babel/core": 7.29.0 + "@jest/types": 30.3.0 + "@jridgewell/trace-mapping": 0.3.31 + babel-plugin-istanbul: 7.0.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.3.0 + jest-regex-util: 30.0.1 + jest-util: 30.3.0 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + + "@jest/types@30.3.0": + dependencies: + "@jest/pattern": 30.0.1 + "@jest/schemas": 30.0.5 + "@types/istanbul-lib-coverage": 2.0.6 + "@types/istanbul-reports": 3.0.4 + "@types/node": 25.5.2 + "@types/yargs": 17.0.35 + chalk: 4.1.2 + + "@jridgewell/gen-mapping@0.3.13": + dependencies: + "@jridgewell/sourcemap-codec": 1.5.5 + "@jridgewell/trace-mapping": 0.3.31 + + "@jridgewell/remapping@2.3.5": + dependencies: + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.31 + "@jridgewell/resolve-uri@3.1.2": {} "@jridgewell/sourcemap-codec@1.5.5": {} + "@jridgewell/trace-mapping@0.3.31": + dependencies: + "@jridgewell/resolve-uri": 3.1.2 + "@jridgewell/sourcemap-codec": 1.5.5 + "@jridgewell/trace-mapping@0.3.9": dependencies: "@jridgewell/resolve-uri": 3.1.2 @@ -4251,6 +6738,13 @@ snapshots: "@lukeed/csprng@1.1.0": {} + "@napi-rs/wasm-runtime@0.2.12": + dependencies: + "@emnapi/core": 1.9.2 + "@emnapi/runtime": 1.9.2 + "@tybys/wasm-util": 0.10.1 + optional: true + "@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)": dependencies: file-type: 21.3.4 @@ -4306,6 +6800,16 @@ snapshots: transitivePeerDependencies: - supports-color + "@nestjs/testing@11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(@nestjs/platform-express@11.1.18)": + dependencies: + "@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + "@nestjs/core": 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + tslib: 2.8.1 + optionalDependencies: + "@nestjs/platform-express": 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18) + + "@noble/hashes@1.8.0": {} + "@noble/hashes@2.0.1": {} "@nuxt/opencollective@0.4.1": @@ -4356,6 +6860,15 @@ snapshots: dependencies: "@otplib/core": 13.4.0 + "@paralleldrive/cuid2@2.3.1": + dependencies: + "@noble/hashes": 1.8.0 + + "@pkgjs/parseargs@0.11.0": + optional: true + + "@pkgr/core@0.2.9": {} + "@prisma/client-runtime-utils@7.6.0": {} "@prisma/client@7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)": @@ -4498,6 +7011,16 @@ snapshots: "@scure/base@2.0.0": {} + "@sinclair/typebox@0.34.49": {} + + "@sinonjs/commons@3.0.1": + dependencies: + type-detect: 4.0.8 + + "@sinonjs/fake-timers@15.3.0": + dependencies: + "@sinonjs/commons": 3.0.1 + "@smithy/chunked-blob-reader-native@4.2.3": dependencies: "@smithy/util-base64": 4.3.2 @@ -4867,6 +7390,32 @@ snapshots: "@turbo/windows-arm64@2.9.3": optional: true + "@tybys/wasm-util@0.10.1": + dependencies: + tslib: 2.8.1 + optional: true + + "@types/babel__core@7.20.5": + dependencies: + "@babel/parser": 7.29.2 + "@babel/types": 7.29.0 + "@types/babel__generator": 7.27.0 + "@types/babel__template": 7.4.4 + "@types/babel__traverse": 7.28.0 + + "@types/babel__generator@7.27.0": + dependencies: + "@babel/types": 7.29.0 + + "@types/babel__template@7.4.4": + dependencies: + "@babel/parser": 7.29.2 + "@babel/types": 7.29.0 + + "@types/babel__traverse@7.28.0": + dependencies: + "@babel/types": 7.29.0 + "@types/body-parser@1.19.6": dependencies: "@types/connect": 3.4.38 @@ -4876,6 +7425,8 @@ snapshots: dependencies: "@types/node": 25.5.2 + "@types/cookiejar@2.1.5": {} + "@types/esrecurse@4.3.1": {} "@types/estree@1.0.8": {} @@ -4895,6 +7446,21 @@ snapshots: "@types/http-errors@2.0.5": {} + "@types/istanbul-lib-coverage@2.0.6": {} + + "@types/istanbul-lib-report@3.0.3": + dependencies: + "@types/istanbul-lib-coverage": 2.0.6 + + "@types/istanbul-reports@3.0.4": + dependencies: + "@types/istanbul-lib-report": 3.0.3 + + "@types/jest@30.0.0": + dependencies: + expect: 30.3.0 + pretty-format: 30.3.0 + "@types/json-schema@7.0.15": {} "@types/jsonwebtoken@9.0.10": @@ -4902,6 +7468,8 @@ snapshots: "@types/ms": 2.1.0 "@types/node": 25.5.2 + "@types/methods@1.1.4": {} + "@types/ms@2.1.0": {} "@types/node@25.5.2": @@ -4945,12 +7513,93 @@ snapshots: "@types/http-errors": 2.0.5 "@types/node": 25.5.2 + "@types/stack-utils@2.0.3": {} + "@types/strip-bom@3.0.0": {} "@types/strip-json-comments@0.0.30": {} + "@types/superagent@8.1.9": + dependencies: + "@types/cookiejar": 2.1.5 + "@types/methods": 1.1.4 + "@types/node": 25.5.2 + form-data: 4.0.5 + + "@types/supertest@7.2.0": + dependencies: + "@types/methods": 1.1.4 + "@types/superagent": 8.1.9 + "@types/validator@13.15.10": {} + "@types/yargs-parser@21.0.3": {} + + "@types/yargs@17.0.35": + dependencies: + "@types/yargs-parser": 21.0.3 + + "@ungap/structured-clone@1.3.0": {} + + "@unrs/resolver-binding-android-arm-eabi@1.11.1": + optional: true + + "@unrs/resolver-binding-android-arm64@1.11.1": + optional: true + + "@unrs/resolver-binding-darwin-arm64@1.11.1": + optional: true + + "@unrs/resolver-binding-darwin-x64@1.11.1": + optional: true + + "@unrs/resolver-binding-freebsd-x64@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-arm64-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-arm64-musl@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-riscv64-musl@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-s390x-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-x64-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-x64-musl@1.11.1": + optional: true + + "@unrs/resolver-binding-wasm32-wasi@1.11.1": + dependencies: + "@napi-rs/wasm-runtime": 0.2.12 + optional: true + + "@unrs/resolver-binding-win32-arm64-msvc@1.11.1": + optional: true + + "@unrs/resolver-binding-win32-ia32-msvc@1.11.1": + optional: true + + "@unrs/resolver-binding-win32-x64-msvc@1.11.1": + optional: true + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -4980,12 +7629,24 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + ansi-escapes@7.3.0: dependencies: environment: 1.1.0 + ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + ansi-styles@6.2.3: {} anymatch@3.1.3: @@ -4997,12 +7658,74 @@ snapshots: arg@4.1.3: {} + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + asap@2.0.6: {} + + asynckit@0.4.0: {} + aws-ssl-profiles@1.1.2: {} + babel-jest@30.3.0(@babel/core@7.29.0): + dependencies: + "@babel/core": 7.29.0 + "@jest/transform": 30.3.0 + "@types/babel__core": 7.20.5 + babel-plugin-istanbul: 7.0.1 + babel-preset-jest: 30.3.0(@babel/core@7.29.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@7.0.1: + dependencies: + "@babel/helper-plugin-utils": 7.28.6 + "@istanbuljs/load-nyc-config": 1.1.0 + "@istanbuljs/schema": 0.1.3 + istanbul-lib-instrument: 6.0.3 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@30.3.0: + dependencies: + "@types/babel__core": 7.20.5 + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + "@babel/core": 7.29.0 + "@babel/plugin-syntax-async-generators": 7.8.4(@babel/core@7.29.0) + "@babel/plugin-syntax-bigint": 7.8.3(@babel/core@7.29.0) + "@babel/plugin-syntax-class-properties": 7.12.13(@babel/core@7.29.0) + "@babel/plugin-syntax-class-static-block": 7.14.5(@babel/core@7.29.0) + "@babel/plugin-syntax-import-attributes": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-syntax-import-meta": 7.10.4(@babel/core@7.29.0) + "@babel/plugin-syntax-json-strings": 7.8.3(@babel/core@7.29.0) + "@babel/plugin-syntax-logical-assignment-operators": 7.10.4(@babel/core@7.29.0) + "@babel/plugin-syntax-nullish-coalescing-operator": 7.8.3(@babel/core@7.29.0) + "@babel/plugin-syntax-numeric-separator": 7.10.4(@babel/core@7.29.0) + "@babel/plugin-syntax-object-rest-spread": 7.8.3(@babel/core@7.29.0) + "@babel/plugin-syntax-optional-catch-binding": 7.8.3(@babel/core@7.29.0) + "@babel/plugin-syntax-optional-chaining": 7.8.3(@babel/core@7.29.0) + "@babel/plugin-syntax-private-property-in-object": 7.14.5(@babel/core@7.29.0) + "@babel/plugin-syntax-top-level-await": 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@30.3.0(@babel/core@7.29.0): + dependencies: + "@babel/core": 7.29.0 + babel-plugin-jest-hoist: 30.3.0 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + balanced-match@1.0.2: {} balanced-match@4.0.4: {} + baseline-browser-mapping@2.10.14: {} + better-result@2.7.0: dependencies: "@clack/prompts": 0.11.0 @@ -5030,6 +7753,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.0.3: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 @@ -5038,6 +7765,22 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.14 + caniuse-lite: 1.0.30001785 + electron-to-chromium: 1.5.331 + node-releases: 2.0.37 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + buffer-equal-constant-time@1.0.1: {} buffer-from@1.1.2: {} @@ -5073,6 +7816,21 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001785: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + char-regex@1.0.2: {} + chart.js@4.5.1: dependencies: "@kurkle/color": 0.3.4 @@ -5093,12 +7851,16 @@ snapshots: dependencies: readdirp: 4.1.2 + ci-info@4.4.0: {} + citty@0.1.6: dependencies: consola: 3.4.2 citty@0.2.2: {} + cjs-module-lexer@2.2.0: {} + class-transformer@0.5.1: {} class-validator@0.15.1: @@ -5116,10 +7878,32 @@ snapshots: slice-ansi: 8.0.0 string-width: 8.2.0 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + co@4.6.0: {} + + collect-v8-coverage@1.0.3: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + colorette@2.0.20: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@14.0.3: {} + component-emitter@1.3.1: {} + concat-map@0.0.1: {} concat-stream@2.0.0: @@ -5137,10 +7921,14 @@ snapshots: content-type@1.0.5: {} + convert-source-map@2.0.0: {} + cookie-signature@1.2.2: {} cookie@0.7.2: {} + cookiejar@2.1.4: {} + cors@2.8.6: dependencies: object-assign: 4.1.1 @@ -5160,18 +7948,31 @@ snapshots: dependencies: ms: 2.1.3 + dedent@1.7.2: {} + deep-is@0.1.4: {} deepmerge-ts@7.1.5: {} + deepmerge@4.3.1: {} + defu@6.1.6: {} + delayed-stream@1.0.0: {} + denque@2.1.0: {} depd@2.0.0: {} destr@2.0.5: {} + detect-newline@3.1.0: {} + + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + diff@4.0.4: {} dotenv-expand@12.0.3: @@ -5192,6 +7993,8 @@ snapshots: dependencies: xtend: 4.0.2 + eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 @@ -5203,8 +8006,16 @@ snapshots: "@standard-schema/spec": 1.1.0 fast-check: 3.23.2 + electron-to-chromium@1.5.331: {} + + emittery@0.13.1: {} + emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + empathic@2.0.0: {} encodeurl@2.0.0: {} @@ -5213,6 +8024,10 @@ snapshots: environment@1.1.0: {} + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -5221,8 +8036,19 @@ snapshots: dependencies: es-errors: 1.3.0 + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@2.0.0: {} + escape-string-regexp@4.0.0: {} eslint-scope@9.1.2: @@ -5279,6 +8105,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 5.0.1 + esprima@4.0.1: {} + esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -5295,6 +8123,29 @@ snapshots: eventemitter3@5.0.4: {} + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit-x@0.2.2: {} + + expect@30.3.0: + dependencies: + "@jest/expect-utils": 30.3.0 + "@jest/get-type": 30.1.0 + jest-matcher-utils: 30.3.0 + jest-message-util: 30.3.0 + jest-mock: 30.3.0 + jest-util: 30.3.0 + express@5.2.1: dependencies: accepts: 2.0.0 @@ -5354,6 +8205,10 @@ snapshots: path-expression-matcher: 1.2.1 strnum: 2.2.2 + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -5382,6 +8237,11 @@ snapshots: transitivePeerDependencies: - supports-color + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -5399,6 +8259,20 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formidable@3.5.4: + dependencies: + "@paralleldrive/cuid2": 2.3.1 + dezalgo: 1.0.4 + once: 1.4.0 + forwarded@0.2.0: {} fresh@2.0.0: {} @@ -5414,6 +8288,10 @@ snapshots: dependencies: is-property: 1.0.2 + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + get-east-asian-width@1.5.0: {} get-intrinsic@1.3.0: @@ -5429,6 +8307,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-package-type@0.1.0: {} + get-port-please@3.2.0: {} get-proto@1.0.1: @@ -5436,6 +8316,8 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@6.0.1: {} + giget@2.0.0: dependencies: citty: 0.1.6 @@ -5453,6 +8335,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -5472,14 +8363,31 @@ snapshots: graphmatch@1.1.1: {} + handlebars@4.7.9: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-flag@4.0.0: {} + has-symbols@1.1.0: {} + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hasown@2.0.2: dependencies: function-bind: 1.1.2 hono@4.12.10: {} + html-escaper@2.0.2: {} + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -5490,6 +8398,8 @@ snapshots: http-status-codes@2.3.0: {} + human-signals@2.1.0: {} + husky@9.1.7: {} iconv-lite@0.7.2: @@ -5500,6 +8410,11 @@ snapshots: ignore@5.3.2: {} + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + imurmurhash@0.1.4: {} inflight@1.0.6: @@ -5511,6 +8426,8 @@ snapshots: ipaddr.js@1.9.1: {} + is-arrayish@0.2.1: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -5521,10 +8438,14 @@ snapshots: is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@5.1.0: dependencies: get-east-asian-width: 1.5.0 + is-generator-fn@2.1.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -5535,20 +8456,383 @@ snapshots: is-property@1.0.2: {} + is-stream@2.0.1: {} + isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@6.0.3: + dependencies: + "@babel/core": 7.29.0 + "@babel/parser": 7.29.2 + "@istanbuljs/schema": 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + "@jridgewell/trace-mapping": 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + iterare@1.2.1: {} + jackspeak@3.4.3: + dependencies: + "@isaacs/cliui": 8.0.2 + optionalDependencies: + "@pkgjs/parseargs": 0.11.0 + + jest-changed-files@30.3.0: + dependencies: + execa: 5.1.1 + jest-util: 30.3.0 + p-limit: 3.1.0 + + jest-circus@30.3.0: + dependencies: + "@jest/environment": 30.3.0 + "@jest/expect": 30.3.0 + "@jest/test-result": 30.3.0 + "@jest/types": 30.3.0 + "@types/node": 25.5.2 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.2 + is-generator-fn: 2.1.0 + jest-each: 30.3.0 + jest-matcher-utils: 30.3.0 + jest-message-util: 30.3.0 + jest-runtime: 30.3.0 + jest-snapshot: 30.3.0 + jest-util: 30.3.0 + p-limit: 3.1.0 + pretty-format: 30.3.0 + pure-rand: 7.0.1 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)): + dependencies: + "@jest/core": 30.3.0(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)) + "@jest/test-result": 30.3.0 + "@jest/types": 30.3.0 + chalk: 4.1.2 + exit-x: 0.2.2 + import-local: 3.2.0 + jest-config: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)) + jest-util: 30.3.0 + jest-validate: 30.3.0 + yargs: 17.7.2 + transitivePeerDependencies: + - "@types/node" + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + jest-config@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)): + dependencies: + "@babel/core": 7.29.0 + "@jest/get-type": 30.1.0 + "@jest/pattern": 30.0.1 + "@jest/test-sequencer": 30.3.0 + "@jest/types": 30.3.0 + babel-jest: 30.3.0(@babel/core@7.29.0) + chalk: 4.1.2 + ci-info: 4.4.0 + deepmerge: 4.3.1 + glob: 10.5.0 + graceful-fs: 4.2.11 + jest-circus: 30.3.0 + jest-docblock: 30.2.0 + jest-environment-node: 30.3.0 + jest-regex-util: 30.0.1 + jest-resolve: 30.3.0 + jest-runner: 30.3.0 + jest-util: 30.3.0 + jest-validate: 30.3.0 + parse-json: 5.2.0 + pretty-format: 30.3.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + "@types/node": 25.5.2 + ts-node: 10.9.2(@types/node@25.5.2)(typescript@5.9.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@30.3.0: + dependencies: + "@jest/diff-sequences": 30.3.0 + "@jest/get-type": 30.1.0 + chalk: 4.1.2 + pretty-format: 30.3.0 + + jest-docblock@30.2.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@30.3.0: + dependencies: + "@jest/get-type": 30.1.0 + "@jest/types": 30.3.0 + chalk: 4.1.2 + jest-util: 30.3.0 + pretty-format: 30.3.0 + + jest-environment-node@30.3.0: + dependencies: + "@jest/environment": 30.3.0 + "@jest/fake-timers": 30.3.0 + "@jest/types": 30.3.0 + "@types/node": 25.5.2 + jest-mock: 30.3.0 + jest-util: 30.3.0 + jest-validate: 30.3.0 + + jest-haste-map@30.3.0: + dependencies: + "@jest/types": 30.3.0 + "@types/node": 25.5.2 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 30.0.1 + jest-util: 30.3.0 + jest-worker: 30.3.0 + picomatch: 4.0.4 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@30.3.0: + dependencies: + "@jest/get-type": 30.1.0 + pretty-format: 30.3.0 + + jest-matcher-utils@30.3.0: + dependencies: + "@jest/get-type": 30.1.0 + chalk: 4.1.2 + jest-diff: 30.3.0 + pretty-format: 30.3.0 + + jest-message-util@30.3.0: + dependencies: + "@babel/code-frame": 7.29.0 + "@jest/types": 30.3.0 + "@types/stack-utils": 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + picomatch: 4.0.4 + pretty-format: 30.3.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@30.3.0: + dependencies: + "@jest/types": 30.3.0 + "@types/node": 25.5.2 + jest-util: 30.3.0 + + jest-pnp-resolver@1.2.3(jest-resolve@30.3.0): + optionalDependencies: + jest-resolve: 30.3.0 + + jest-regex-util@30.0.1: {} + + jest-resolve-dependencies@30.3.0: + dependencies: + jest-regex-util: 30.0.1 + jest-snapshot: 30.3.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@30.3.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 30.3.0 + jest-pnp-resolver: 1.2.3(jest-resolve@30.3.0) + jest-util: 30.3.0 + jest-validate: 30.3.0 + slash: 3.0.0 + unrs-resolver: 1.11.1 + + jest-runner@30.3.0: + dependencies: + "@jest/console": 30.3.0 + "@jest/environment": 30.3.0 + "@jest/test-result": 30.3.0 + "@jest/transform": 30.3.0 + "@jest/types": 30.3.0 + "@types/node": 25.5.2 + chalk: 4.1.2 + emittery: 0.13.1 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-docblock: 30.2.0 + jest-environment-node: 30.3.0 + jest-haste-map: 30.3.0 + jest-leak-detector: 30.3.0 + jest-message-util: 30.3.0 + jest-resolve: 30.3.0 + jest-runtime: 30.3.0 + jest-util: 30.3.0 + jest-watcher: 30.3.0 + jest-worker: 30.3.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@30.3.0: + dependencies: + "@jest/environment": 30.3.0 + "@jest/fake-timers": 30.3.0 + "@jest/globals": 30.3.0 + "@jest/source-map": 30.0.1 + "@jest/test-result": 30.3.0 + "@jest/transform": 30.3.0 + "@jest/types": 30.3.0 + "@types/node": 25.5.2 + chalk: 4.1.2 + cjs-module-lexer: 2.2.0 + collect-v8-coverage: 1.0.3 + glob: 10.5.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.3.0 + jest-message-util: 30.3.0 + jest-mock: 30.3.0 + jest-regex-util: 30.0.1 + jest-resolve: 30.3.0 + jest-snapshot: 30.3.0 + jest-util: 30.3.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@30.3.0: + dependencies: + "@babel/core": 7.29.0 + "@babel/generator": 7.29.1 + "@babel/plugin-syntax-jsx": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-syntax-typescript": 7.28.6(@babel/core@7.29.0) + "@babel/types": 7.29.0 + "@jest/expect-utils": 30.3.0 + "@jest/get-type": 30.1.0 + "@jest/snapshot-utils": 30.3.0 + "@jest/transform": 30.3.0 + "@jest/types": 30.3.0 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + chalk: 4.1.2 + expect: 30.3.0 + graceful-fs: 4.2.11 + jest-diff: 30.3.0 + jest-matcher-utils: 30.3.0 + jest-message-util: 30.3.0 + jest-util: 30.3.0 + pretty-format: 30.3.0 + semver: 7.7.4 + synckit: 0.11.12 + transitivePeerDependencies: + - supports-color + + jest-util@30.3.0: + dependencies: + "@jest/types": 30.3.0 + "@types/node": 25.5.2 + chalk: 4.1.2 + ci-info: 4.4.0 + graceful-fs: 4.2.11 + picomatch: 4.0.4 + + jest-validate@30.3.0: + dependencies: + "@jest/get-type": 30.1.0 + "@jest/types": 30.3.0 + camelcase: 6.3.0 + chalk: 4.1.2 + leven: 3.1.0 + pretty-format: 30.3.0 + + jest-watcher@30.3.0: + dependencies: + "@jest/test-result": 30.3.0 + "@jest/types": 30.3.0 + "@types/node": 25.5.2 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 30.3.0 + string-length: 4.0.2 + + jest-worker@30.3.0: + dependencies: + "@types/node": 25.5.2 + "@ungap/structured-clone": 1.3.0 + jest-util: 30.3.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)): + dependencies: + "@jest/core": 30.3.0(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)) + "@jest/types": 30.3.0 + import-local: 3.2.0 + jest-cli: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)) + transitivePeerDependencies: + - "@types/node" + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + jiti@2.6.1: {} + js-tokens@4.0.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + jsesc@3.1.0: {} + json-buffer@3.0.1: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + jsonwebtoken@9.0.3: dependencies: jws: 4.0.1 @@ -5577,6 +8861,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + leven@3.1.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -5584,6 +8870,8 @@ snapshots: libphonenumber-js@1.12.41: {} + lines-and-columns@1.2.4: {} + lint-staged@16.4.0: dependencies: commander: 14.0.3 @@ -5604,6 +8892,10 @@ snapshots: load-esm@1.0.3: {} + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -5620,6 +8912,8 @@ snapshots: lodash.isstring@4.0.1: {} + lodash.memoize@4.1.2: {} + lodash.once@4.1.1: {} lodash@4.17.23: {} @@ -5634,10 +8928,24 @@ snapshots: long@5.3.2: {} + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lru.min@1.1.4: {} + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + make-error@1.3.6: {} + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + math-intrinsics@1.1.0: {} media-typer@0.3.0: {} @@ -5646,6 +8954,10 @@ snapshots: merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} + + methods@1.1.2: {} + mime-db@1.52.0: {} mime-db@1.54.0: {} @@ -5658,6 +8970,10 @@ snapshots: dependencies: mime-db: 1.54.0 + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + mimic-function@5.0.1: {} minimatch@10.2.5: @@ -5668,8 +8984,14 @@ snapshots: dependencies: brace-expansion: 1.1.13 + minimatch@9.0.9: + dependencies: + brace-expansion: 2.0.3 + minimist@1.2.8: {} + minipass@7.1.3: {} + mkdirp@1.0.4: {} ms@2.1.3: {} @@ -5697,14 +9019,26 @@ snapshots: dependencies: lru.min: 1.1.4 + napi-postinstall@0.3.4: {} + natural-compare@1.4.0: {} negotiator@1.0.0: {} + neo-async@2.6.2: {} + node-fetch-native@1.6.7: {} + node-int64@0.4.0: {} + + node-releases@2.0.37: {} + normalize-path@3.0.0: {} + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + nypm@0.6.5: dependencies: citty: 0.2.2 @@ -5725,6 +9059,10 @@ snapshots: dependencies: wrappy: 1.0.2 + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -5747,14 +9085,33 @@ snapshots: "@otplib/totp": 13.4.0 "@otplib/uri": 13.4.0 + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + parse-json@5.2.0: + dependencies: + "@babel/code-frame": 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -5767,6 +9124,11 @@ snapshots: path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + path-to-regexp@8.4.2: {} pathe@2.0.3: {} @@ -5779,6 +9141,12 @@ snapshots: picomatch@4.0.4: {} + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + pkg-types@2.3.0: dependencies: confbox: 0.2.4 @@ -5791,6 +9159,12 @@ snapshots: prettier@3.8.1: {} + pretty-format@30.3.0: + dependencies: + "@jest/schemas": 30.0.5 + ansi-styles: 5.2.0 + react-is: 18.3.1 + prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): dependencies: "@prisma/config": 7.6.0 @@ -5823,6 +9197,8 @@ snapshots: pure-rand@6.1.0: {} + pure-rand@7.0.1: {} + qs@6.15.0: dependencies: side-channel: 1.1.0 @@ -5846,6 +9222,8 @@ snapshots: react: 19.2.4 scheduler: 0.27.0 + react-is@18.3.1: {} + react@19.2.4: {} readable-stream@3.6.2: @@ -5864,8 +9242,16 @@ snapshots: remeda@2.33.4: {} + require-directory@2.1.1: {} + require-from-string@2.0.2: {} + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@5.0.0: {} + resolve@1.22.11: dependencies: is-core-module: 2.16.1 @@ -5905,6 +9291,8 @@ snapshots: scheduler@0.27.0: {} + semver@6.3.1: {} + semver@7.7.4: {} send@1.2.1: @@ -5976,6 +9364,8 @@ snapshots: sisteransi@1.0.5: {} + slash@3.0.0: {} + slice-ansi@7.1.2: dependencies: ansi-styles: 6.2.3 @@ -5986,6 +9376,11 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -5993,8 +9388,14 @@ snapshots: source-map@0.6.1: {} + sprintf-js@1.0.3: {} + sqlstring@2.3.3: {} + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + statuses@2.0.2: {} std-env@3.10.0: {} @@ -6003,6 +9404,23 @@ snapshots: string-argv@0.3.2: {} + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -6018,26 +9436,78 @@ snapshots: dependencies: safe-buffer: 5.2.1 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 strip-bom@3.0.0: {} + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} + strnum@2.2.2: {} strtok3@10.3.5: dependencies: "@tokenizer/token": 0.3.0 + superagent@10.3.0: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.4.3 + 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.15.0 + transitivePeerDependencies: + - supports-color + + supertest@7.2.2: + dependencies: + cookie-signature: 1.2.2 + methods: 1.1.2 + superagent: 10.3.0 + transitivePeerDependencies: + - supports-color + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} + synckit@0.11.12: + dependencies: + "@pkgr/core": 0.2.9 + + test-exclude@6.0.0: + dependencies: + "@istanbuljs/schema": 0.1.3 + glob: 7.2.3 + minimatch: 3.1.5 + thirty-two@1.0.2: {} tinyexec@1.0.4: {} + tmpl@1.0.5: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -6052,6 +9522,26 @@ snapshots: tree-kill@1.2.2: {} + ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@30.3.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)))(typescript@5.9.3): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.9 + jest: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.4 + type-fest: 4.41.0 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionalDependencies: + "@babel/core": 7.29.0 + "@jest/transform": 30.3.0 + "@jest/types": 30.3.0 + babel-jest: 30.3.0(@babel/core@7.29.0) + jest-util: 30.3.0 + ts-node-dev@2.0.0(@types/node@25.5.2)(typescript@5.9.3): dependencies: chokidar: 3.6.0 @@ -6110,6 +9600,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + + type-fest@4.41.0: {} + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -6125,6 +9621,9 @@ snapshots: typescript@5.9.3: {} + uglify-js@3.19.3: + optional: true + uid@2.0.2: dependencies: "@lukeed/csprng": 1.1.0 @@ -6135,6 +9634,36 @@ snapshots: unpipe@1.0.0: {} + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + "@unrs/resolver-binding-android-arm-eabi": 1.11.1 + "@unrs/resolver-binding-android-arm64": 1.11.1 + "@unrs/resolver-binding-darwin-arm64": 1.11.1 + "@unrs/resolver-binding-darwin-x64": 1.11.1 + "@unrs/resolver-binding-freebsd-x64": 1.11.1 + "@unrs/resolver-binding-linux-arm-gnueabihf": 1.11.1 + "@unrs/resolver-binding-linux-arm-musleabihf": 1.11.1 + "@unrs/resolver-binding-linux-arm64-gnu": 1.11.1 + "@unrs/resolver-binding-linux-arm64-musl": 1.11.1 + "@unrs/resolver-binding-linux-ppc64-gnu": 1.11.1 + "@unrs/resolver-binding-linux-riscv64-gnu": 1.11.1 + "@unrs/resolver-binding-linux-riscv64-musl": 1.11.1 + "@unrs/resolver-binding-linux-s390x-gnu": 1.11.1 + "@unrs/resolver-binding-linux-x64-gnu": 1.11.1 + "@unrs/resolver-binding-linux-x64-musl": 1.11.1 + "@unrs/resolver-binding-wasm32-wasi": 1.11.1 + "@unrs/resolver-binding-win32-arm64-msvc": 1.11.1 + "@unrs/resolver-binding-win32-ia32-msvc": 1.11.1 + "@unrs/resolver-binding-win32-x64-msvc": 1.11.1 + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -6143,6 +9672,12 @@ snapshots: v8-compile-cache-lib@3.0.1: {} + v8-to-istanbul@9.3.0: + dependencies: + "@jridgewell/trace-mapping": 0.3.31 + "@types/istanbul-lib-coverage": 2.0.6 + convert-source-map: 2.0.0 + valibot@1.2.0(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -6151,12 +9686,30 @@ snapshots: vary@1.1.2: {} + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + which@2.0.2: dependencies: isexe: 2.0.0 word-wrap@1.2.5: {} + wordwrap@1.0.0: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 @@ -6165,10 +9718,31 @@ snapshots: wrappy@1.0.2: {} + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + xtend@4.0.2: {} + y18n@5.0.8: {} + + yallist@3.1.1: {} + yaml@2.8.3: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yn@3.1.1: {} yocto-queue@0.1.0: {} From e84bef07b422ba160cd12e31b23e361ae963e8a7 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 01:07:05 +0800 Subject: [PATCH 25/80] fix(api-prisma): initialize prisma client with pg adapter --- apps/api/package.json | 2 + apps/api/src/prisma/prisma.service.ts | 15 ++ pnpm-lock.yaml | 192 ++++++++++++++++++++++++++ 3 files changed, 209 insertions(+) diff --git a/apps/api/package.json b/apps/api/package.json index 2b3fe6d..53898ec 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -39,10 +39,12 @@ "@nestjs/jwt": "^11.0.2", "@nestjs/platform-express": "^11.1.18", "@otplib/preset-default": "^12.0.1", + "@prisma/adapter-pg": "^7.6.0", "@prisma/client": "^7.6.0", "class-transformer": "^0.5.1", "class-validator": "^0.15.1", "otplib": "^13.4.0", + "pg": "^8.20.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2" } diff --git a/apps/api/src/prisma/prisma.service.ts b/apps/api/src/prisma/prisma.service.ts index 72e40f6..27e6013 100644 --- a/apps/api/src/prisma/prisma.service.ts +++ b/apps/api/src/prisma/prisma.service.ts @@ -1,8 +1,23 @@ import { Injectable, OnModuleDestroy, OnModuleInit } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { PrismaPg } from "@prisma/adapter-pg"; import { PrismaClient } from "../../generated/prisma/client"; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { + constructor(configService: ConfigService) { + const connectionString = configService.get("DATABASE_URL"); + if (!connectionString) { + throw new Error("缺少数据库连接配置 DATABASE_URL"); + } + + super({ + adapter: new PrismaPg({ + connectionString + }) + }); + } + async onModuleInit(): Promise { await this.$connect(); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a87e0fd..364478b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,6 +55,9 @@ importers: "@otplib/preset-default": specifier: ^12.0.1 version: 12.0.1 + "@prisma/adapter-pg": + specifier: ^7.6.0 + version: 7.6.0 "@prisma/client": specifier: ^7.6.0 version: 7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) @@ -67,6 +70,9 @@ importers: otplib: specifier: ^13.4.0 version: 13.4.0 + pg: + specifier: ^8.20.0 + version: 8.20.0 reflect-metadata: specifier: ^0.2.2 version: 0.2.2 @@ -1248,6 +1254,12 @@ packages: } engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + "@prisma/adapter-pg@7.6.0": + resolution: + { + integrity: sha512-BjHNmJqqa42NqJSDPnXUfwUofWo8LJY7Ui2gqxN4DmAOb+H/gGKv+hln2Xq/1kSJXPW5AXMXuNiPDMpywvyIOw== + } + "@prisma/client-runtime-utils@7.6.0": resolution: { @@ -1293,6 +1305,12 @@ packages: integrity: sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg== } + "@prisma/driver-adapter-utils@7.6.0": + resolution: + { + integrity: sha512-D8j3p0RnhLuufMaRLX6QqtGgPC5Ao3l5oFP6Q5AL0rTHi4vna+NzGEipwCsfvcSvaGFCbsH3lsTMbb4WvY+ovA== + } + "@prisma/engines-version@7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711": resolution: { @@ -2087,6 +2105,12 @@ packages: integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg== } + "@types/pg@8.20.0": + resolution: + { + integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow== + } + "@types/qs@6.15.0": resolution: { @@ -4729,6 +4753,64 @@ packages: integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA== } + pg-cloudflare@1.3.0: + resolution: + { + integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ== + } + + pg-connection-string@2.12.0: + resolution: + { + integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ== + } + + pg-int8@1.0.1: + resolution: + { + integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + } + engines: { node: ">=4.0.0" } + + pg-pool@3.13.0: + resolution: + { + integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA== + } + peerDependencies: + pg: ">=8.0" + + pg-protocol@1.13.0: + resolution: + { + integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w== + } + + pg-types@2.2.0: + resolution: + { + integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + } + engines: { node: ">=4" } + + pg@8.20.0: + resolution: + { + integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA== + } + engines: { node: ">= 16.0.0" } + peerDependencies: + pg-native: ">=3.0.1" + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: + { + integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + } + picocolors@1.1.1: resolution: { @@ -4769,6 +4851,41 @@ packages: integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig== } + postgres-array@2.0.0: + resolution: + { + integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + } + engines: { node: ">=4" } + + postgres-array@3.0.4: + resolution: + { + integrity: sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ== + } + engines: { node: ">=12" } + + postgres-bytea@1.0.1: + resolution: + { + integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ== + } + engines: { node: ">=0.10.0" } + + postgres-date@1.0.7: + resolution: + { + integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + } + engines: { node: ">=0.10.0" } + + postgres-interval@1.2.0: + resolution: + { + integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + } + engines: { node: ">=0.10.0" } + postgres@3.4.7: resolution: { @@ -5164,6 +5281,13 @@ packages: } engines: { node: ">=0.10.0" } + split2@4.2.0: + resolution: + { + integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + } + engines: { node: ">= 10.x" } + sprintf-js@1.0.3: resolution: { @@ -6869,6 +6993,15 @@ snapshots: "@pkgr/core@0.2.9": {} + "@prisma/adapter-pg@7.6.0": + dependencies: + "@prisma/driver-adapter-utils": 7.6.0 + "@types/pg": 8.20.0 + pg: 8.20.0 + postgres-array: 3.0.4 + transitivePeerDependencies: + - pg-native + "@prisma/client-runtime-utils@7.6.0": {} "@prisma/client@7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)": @@ -6913,6 +7046,10 @@ snapshots: transitivePeerDependencies: - typescript + "@prisma/driver-adapter-utils@7.6.0": + dependencies: + "@prisma/debug": 7.6.0 + "@prisma/engines-version@7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711": {} "@prisma/engines@7.6.0": @@ -7496,6 +7633,12 @@ snapshots: dependencies: "@types/express": 5.0.6 + "@types/pg@8.20.0": + dependencies: + "@types/node": 25.5.2 + pg-protocol: 1.13.0 + pg-types: 2.2.0 + "@types/qs@6.15.0": {} "@types/range-parser@1.2.7": {} @@ -9135,6 +9278,41 @@ snapshots: perfect-debounce@1.0.0: {} + pg-cloudflare@1.3.0: + optional: true + + pg-connection-string@2.12.0: {} + + pg-int8@1.0.1: {} + + pg-pool@3.13.0(pg@8.20.0): + dependencies: + pg: 8.20.0 + + pg-protocol@1.13.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.20.0: + dependencies: + pg-connection-string: 2.12.0 + pg-pool: 3.13.0(pg@8.20.0) + pg-protocol: 1.13.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.3.0 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -9153,6 +9331,18 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + postgres-array@2.0.0: {} + + postgres-array@3.0.4: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + postgres@3.4.7: {} prelude-ls@1.2.1: {} @@ -9388,6 +9578,8 @@ snapshots: source-map@0.6.1: {} + split2@4.2.0: {} + sprintf-js@1.0.3: {} sqlstring@2.3.3: {} From 99e5622234011dacf213adb0dd7f9da388940a42 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 02:14:20 +0800 Subject: [PATCH 26/80] fix(ci): ensure prisma client generation and auth deps for typecheck --- apps/api/package.json | 7 +++ pnpm-lock.yaml | 116 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) diff --git a/apps/api/package.json b/apps/api/package.json index 53898ec..66837c0 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -6,6 +6,9 @@ "prisma:generate": "prisma generate", "prisma:format": "prisma format", "prisma:validate": "prisma validate", + "prebuild": "pnpm run prisma:generate", + "pretypecheck": "pnpm run prisma:generate", + "pretest": "pnpm run prisma:generate", "start": "node dist/main.js", "start:dev": "ts-node-dev --respawn --transpile-only src/main.ts", "build": "tsc -p tsconfig.build.json", @@ -37,6 +40,7 @@ "@nestjs/config": "^4.0.3", "@nestjs/core": "^11.1.18", "@nestjs/jwt": "^11.0.2", + "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.1.18", "@otplib/preset-default": "^12.0.1", "@prisma/adapter-pg": "^7.6.0", @@ -44,6 +48,9 @@ "class-transformer": "^0.5.1", "class-validator": "^0.15.1", "otplib": "^13.4.0", + "passport": "^0.7.0", + "passport-github2": "^0.1.12", + "passport-oauth2": "^1.8.0", "pg": "^8.20.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 364478b..799a176 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,6 +49,9 @@ importers: "@nestjs/jwt": specifier: ^11.0.2 version: 11.0.2(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)) + "@nestjs/passport": + specifier: ^11.0.5 + version: 11.0.5(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0) "@nestjs/platform-express": specifier: ^11.1.18 version: 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18) @@ -70,6 +73,15 @@ importers: otplib: specifier: ^13.4.0 version: 13.4.0 + passport: + specifier: ^0.7.0 + version: 0.7.0 + passport-github2: + specifier: ^0.1.12 + version: 0.1.12 + passport-oauth2: + specifier: ^1.8.0 + version: 1.8.0 pg: specifier: ^8.20.0 version: 8.20.0 @@ -1124,6 +1136,15 @@ packages: peerDependencies: "@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + "@nestjs/passport@11.0.5": + resolution: + { + integrity: sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ== + } + peerDependencies: + "@nestjs/common": ^10.0.0 || ^11.0.0 + passport: ^0.5.0 || ^0.6.0 || ^0.7.0 + "@nestjs/platform-express@11.1.18": resolution: { @@ -2535,6 +2556,13 @@ packages: } engines: { node: 18 || 20 || >=22 } + base64url@3.0.1: + resolution: + { + integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + } + engines: { node: ">=6.0.0" } + baseline-browser-mapping@2.10.14: resolution: { @@ -4579,6 +4607,12 @@ packages: engines: { node: ">=18" } hasBin: true + oauth@0.10.2: + resolution: + { + integrity: sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q== + } + object-assign@4.1.1: resolution: { @@ -4694,6 +4728,34 @@ packages: } engines: { node: ">= 0.8" } + passport-github2@0.1.12: + resolution: + { + integrity: sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw== + } + engines: { node: ">= 0.8.0" } + + passport-oauth2@1.8.0: + resolution: + { + integrity: sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA== + } + engines: { node: ">= 0.4.0" } + + passport-strategy@1.0.0: + resolution: + { + integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== + } + engines: { node: ">= 0.4.0" } + + passport@0.7.0: + resolution: + { + integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ== + } + engines: { node: ">= 0.4.0" } + path-exists@4.0.0: resolution: { @@ -4747,6 +4809,12 @@ packages: integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== } + pause@0.0.1: + resolution: + { + integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== + } + perfect-debounce@1.0.0: resolution: { @@ -5679,6 +5747,12 @@ packages: engines: { node: ">=0.8.0" } hasBin: true + uid2@0.0.4: + resolution: + { + integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA== + } + uid@2.0.2: resolution: { @@ -5733,6 +5807,13 @@ packages: integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== } + utils-merge@1.0.1: + resolution: + { + integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + } + engines: { node: ">= 0.4.0" } + v8-compile-cache-lib@3.0.1: resolution: { @@ -6912,6 +6993,11 @@ snapshots: "@types/jsonwebtoken": 9.0.10 jsonwebtoken: 9.0.3 + "@nestjs/passport@11.0.5(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)": + dependencies: + "@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + passport: 0.7.0 + "@nestjs/platform-express@11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)": dependencies: "@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -7867,6 +7953,8 @@ snapshots: balanced-match@4.0.4: {} + base64url@3.0.1: {} + baseline-browser-mapping@2.10.14: {} better-result@2.7.0: @@ -9188,6 +9276,8 @@ snapshots: pathe: 2.0.3 tinyexec: 1.0.4 + oauth@0.10.2: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -9257,6 +9347,26 @@ snapshots: parseurl@1.3.3: {} + passport-github2@0.1.12: + dependencies: + passport-oauth2: 1.8.0 + + passport-oauth2@1.8.0: + dependencies: + base64url: 3.0.1 + oauth: 0.10.2 + passport-strategy: 1.0.0 + uid2: 0.0.4 + utils-merge: 1.0.1 + + passport-strategy@1.0.0: {} + + passport@0.7.0: + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 + path-exists@4.0.0: {} path-expression-matcher@1.2.1: {} @@ -9276,6 +9386,8 @@ snapshots: pathe@2.0.3: {} + pause@0.0.1: {} + perfect-debounce@1.0.0: {} pg-cloudflare@1.3.0: @@ -9816,6 +9928,8 @@ snapshots: uglify-js@3.19.3: optional: true + uid2@0.0.4: {} + uid@2.0.2: dependencies: "@lukeed/csprng": 1.1.0 @@ -9862,6 +9976,8 @@ snapshots: util-deprecate@1.0.2: {} + utils-merge@1.0.1: {} + v8-compile-cache-lib@3.0.1: {} v8-to-istanbul@9.3.0: From 579d63d39d70f606d71185a5872b7aede14961f9 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 02:31:18 +0800 Subject: [PATCH 27/80] feat(web): bootstrap react vite tailwind shadcn stack --- apps/web/.gitignore | 24 + apps/web/.gitkeep | 0 apps/web/README.md | 73 + apps/web/components.json | 25 + apps/web/eslint.config.js | 23 + apps/web/index.html | 13 + apps/web/package.json | 41 + apps/web/postcss.config.js | 6 + apps/web/public/favicon.svg | 1 + apps/web/public/icons.svg | 24 + apps/web/src/App.css | 184 ++ apps/web/src/App.tsx | 27 + apps/web/src/assets/hero.png | Bin 0 -> 44919 bytes apps/web/src/assets/react.svg | 1 + apps/web/src/assets/vite.svg | 1 + apps/web/src/components/ui/button.tsx | 59 + apps/web/src/index.css | 21 + apps/web/src/lib/utils.ts | 6 + apps/web/src/main.tsx | 10 + apps/web/tailwind.config.js | 8 + apps/web/tsconfig.app.json | 32 + apps/web/tsconfig.json | 10 + apps/web/tsconfig.node.json | 26 + apps/web/vite.config.ts | 13 + pnpm-lock.yaml | 3369 ++++++++++++++++++++++++- 25 files changed, 3986 insertions(+), 11 deletions(-) create mode 100644 apps/web/.gitignore delete mode 100644 apps/web/.gitkeep create mode 100644 apps/web/README.md create mode 100644 apps/web/components.json create mode 100644 apps/web/eslint.config.js create mode 100644 apps/web/index.html create mode 100644 apps/web/package.json create mode 100644 apps/web/postcss.config.js create mode 100644 apps/web/public/favicon.svg create mode 100644 apps/web/public/icons.svg create mode 100644 apps/web/src/App.css create mode 100644 apps/web/src/App.tsx create mode 100644 apps/web/src/assets/hero.png create mode 100644 apps/web/src/assets/react.svg create mode 100644 apps/web/src/assets/vite.svg create mode 100644 apps/web/src/components/ui/button.tsx create mode 100644 apps/web/src/index.css create mode 100644 apps/web/src/lib/utils.ts create mode 100644 apps/web/src/main.tsx create mode 100644 apps/web/tailwind.config.js create mode 100644 apps/web/tsconfig.app.json create mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/tsconfig.node.json create mode 100644 apps/web/vite.config.ts diff --git a/apps/web/.gitignore b/apps/web/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/apps/web/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/apps/web/.gitkeep b/apps/web/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/apps/web/README.md b/apps/web/README.md new file mode 100644 index 0000000..2b4caa6 --- /dev/null +++ b/apps/web/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(["dist"]), + { + files: ["**/*.{ts,tsx}"], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ["./tsconfig.node.json", "./tsconfig.app.json"], + tsconfigRootDir: import.meta.dirname + } + // other options... + } + } +]); +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from "eslint-plugin-react-x"; +import reactDom from "eslint-plugin-react-dom"; + +export default defineConfig([ + globalIgnores(["dist"]), + { + files: ["**/*.{ts,tsx}"], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs["recommended-typescript"], + // Enable lint rules for React DOM + reactDom.configs.recommended + ], + languageOptions: { + parserOptions: { + project: ["./tsconfig.node.json", "./tsconfig.app.json"], + tsconfigRootDir: import.meta.dirname + } + // other options... + } + } +]); +``` diff --git a/apps/web/components.json b/apps/web/components.json new file mode 100644 index 0000000..f5ebd86 --- /dev/null +++ b/apps/web/components.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "base-nova", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "menuColor": "default", + "menuAccent": "subtle", + "registries": {} +} diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js new file mode 100644 index 0000000..99368a6 --- /dev/null +++ b/apps/web/eslint.config.js @@ -0,0 +1,23 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; +import { defineConfig, globalIgnores } from "eslint/config"; + +export default defineConfig([ + globalIgnores(["dist"]), + { + files: ["**/*.{ts,tsx}"], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser + } + } +]); diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..5e3836a --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,13 @@ + + + + + + + web + + +
+ + + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..cdebd8a --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,41 @@ +{ + "name": "web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@base-ui/react": "^1.3.0", + "@fontsource-variable/geist": "^5.2.8", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.7.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "shadcn": "^4.1.2", + "tailwind-merge": "^3.5.0", + "tw-animate-css": "^1.4.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.4", + "@types/node": "^24.12.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "autoprefixer": "^10.4.27", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.4.0", + "postcss": "^8.5.8", + "tailwindcss": "^3.4.17", + "typescript": "~5.9.3", + "typescript-eslint": "^8.57.0", + "vite": "^8.0.1" + } +} diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js new file mode 100644 index 0000000..ba80730 --- /dev/null +++ b/apps/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +}; diff --git a/apps/web/public/favicon.svg b/apps/web/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/apps/web/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons.svg b/apps/web/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/apps/web/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/src/App.css b/apps/web/src/App.css new file mode 100644 index 0000000..f90339d --- /dev/null +++ b/apps/web/src/App.css @@ -0,0 +1,184 @@ +.counter { + font-size: 16px; + padding: 5px 10px; + border-radius: 5px; + color: var(--accent); + background: var(--accent-bg); + border: 2px solid transparent; + transition: border-color 0.3s; + margin-bottom: 24px; + + &:hover { + border-color: var(--accent-border); + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } +} + +.hero { + position: relative; + + .base, + .framework, + .vite { + inset-inline: 0; + margin: 0 auto; + } + + .base { + width: 170px; + position: relative; + z-index: 0; + } + + .framework, + .vite { + position: absolute; + } + + .framework { + z-index: 1; + top: 34px; + height: 28px; + transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) + scale(1.4); + } + + .vite { + z-index: 0; + top: 107px; + height: 26px; + width: auto; + transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) + scale(0.8); + } +} + +#center { + display: flex; + flex-direction: column; + gap: 25px; + place-content: center; + place-items: center; + flex-grow: 1; + + @media (max-width: 1024px) { + padding: 32px 20px 24px; + gap: 18px; + } +} + +#next-steps { + display: flex; + border-top: 1px solid var(--border); + text-align: left; + + & > div { + flex: 1 1 0; + padding: 32px; + @media (max-width: 1024px) { + padding: 24px 20px; + } + } + + .icon { + margin-bottom: 16px; + width: 22px; + height: 22px; + } + + @media (max-width: 1024px) { + flex-direction: column; + text-align: center; + } +} + +#docs { + border-right: 1px solid var(--border); + + @media (max-width: 1024px) { + border-right: none; + border-bottom: 1px solid var(--border); + } +} + +#next-steps ul { + list-style: none; + padding: 0; + display: flex; + gap: 8px; + margin: 32px 0 0; + + .logo { + height: 18px; + } + + a { + color: var(--text-h); + font-size: 16px; + border-radius: 6px; + background: var(--social-bg); + display: flex; + padding: 6px 12px; + align-items: center; + gap: 8px; + text-decoration: none; + transition: box-shadow 0.3s; + + &:hover { + box-shadow: var(--shadow); + } + .button-icon { + height: 18px; + width: 18px; + } + } + + @media (max-width: 1024px) { + margin-top: 20px; + flex-wrap: wrap; + justify-content: center; + + li { + flex: 1 1 calc(50% - 8px); + } + + a { + width: 100%; + justify-content: center; + box-sizing: border-box; + } + } +} + +#spacer { + height: 88px; + border-top: 1px solid var(--border); + @media (max-width: 1024px) { + height: 48px; + } +} + +.ticks { + position: relative; + width: 100%; + + &::before, + &::after { + content: ''; + position: absolute; + top: -4.5px; + border: 5px solid transparent; + } + + &::before { + left: 0; + border-left-color: var(--border); + } + &::after { + right: 0; + border-right-color: var(--border); + } +} diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx new file mode 100644 index 0000000..61e4804 --- /dev/null +++ b/apps/web/src/App.tsx @@ -0,0 +1,27 @@ +import { Button } from "@/components/ui/button"; + +function App() { + return ( +
+
+
+
+
+ TodoList +
+ +
+
+
+
+

Web 壳已就绪

+

+ 下一步将接入邮箱验证码登录、OAuth 回调和会话恢复。 +

+
+
+
+ ); +} + +export default App; diff --git a/apps/web/src/assets/hero.png b/apps/web/src/assets/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..cc51a3d20ad4bc961b596a6adfd686685cd84bb0 GIT binary patch literal 44919 zcma%i^5TDbT`tlgo2c`(n!ND-Q6MGAYIbZ-QCh5-QC^YozK_ne*b_MKK#O- zIWy zd$aJVZ?rl%;eiC7d#Sl-cWLv9rA0(UOX(@I3k&yyL+3GaQ4xpb1EGC|i|{byaTI># zBO=0pyZu5XO!hzGNPch4cx%6XJAJpDa<+98BOcYNo1=XER1sv!UW z^>ZDMp%FSmVnt)n^EIR+Nth`vRO^_=UF3EWv75ym{S;#2F8MPot@-y$>ioj!)a1bE zijXPQY;U`qNwl9|wl{W>{FhMSb<>m4{;8Udp4psl)NwFRo(W-T)Y6-qDf=L#U?g<@ zV+T|3+RuE~!E&nodKrkfPcOpJ)&1|p`Tbtd12@MSE8DjWkD|9M>GZsHLf>TTbLx)B z#5K5l%gS7s(yWk?Lj{Nvm`Z-s8xb-Xr`5-xRr%w8v>!oSz{dN*MmxbscQl#Z40qSd z!PQXs-utLEF&$@S#__Lo*pOhG{l(%jyCh-0ME8owiT>U~r&q@MaDRePL(aZAAff9= zBd@*7RZxmiqK^nZH7`bTjIEQw#Y=V6(h{$>7ZIf=7S0;$8~4NXLd4T;Ai~C8&3k-; zYEtJWq6x$#5rrCJ%zspgO z((R)&>BIkkr^qQSEZljO*B+ZDvTeBKJ9N%8Ej=U+62GI)dc|ZMEM66~W12v&QFAIS zoDs`J`wjsl?WdE(NTnjCO!^yB>{yU-2UPT`&FOyVQVmxy#un2Po>GiPPfzd0M^d_i z+Kr}dPhIfsDLd~jOiJ(sHTN;2u)@MaX&0AdXR;BAwr_;1sR;)MM+&{XTzNnKWH@0a zoy9ApaUt=>jjHICu3W42)5;nzHS!M3?aOvZfv-sIc%wc9#l0uHFc}aS4JSrIDOQ?4ri_bS?pjH{U{6qr+6m z--%u=5oc&PxE==-I$~$5gw}yiu_y_o?|ag2+rAgSg%G)}EU}r%*A|v|pjbE`lxJpU zy0{?;(US(i-TiKq6s_(KTYy|YVi&!plMT)EJ4wMU{C7Y;!Xow1nJ+X@ks@r0v25R; z*o$8AP*G*f3$UlYR~18PxKyPj9vU#v)4#GgEx4*?KOhlh>0%3M$-LN7&b*0fXgm$k zH78>bObkx^3_K+RY;G+Usy6L}p9iT!hlnJCmR=;=JL1TdtB#vL!RTJ1TABQx8Ux0w zl^{Jkf(hU>-jr59iK_v-PkV!WwG!LvW<@{3{IbbSiWBrX@S8^`8JFRrc+(AqsUIvm zCTstACtCZ~qy-5^Gr@_z#X!N1*1vH=7@8oL4AEOxWl^YW&LW|1$1J?gG061vk1epe zRI_*s(lrX?-2#tCt_`)p?{zZC+)onl60CU~%4!vPA}h0+fB9ucNkTQ3u29((9Wq=> z^JUm|{_2-=?dMKu&9)#x{lgPOCM`U1^tXDbmZ%I$0fw7|Y-@3Tyj1LGfk$lvzYC85 z=R()QEER%Dz=mTMZ=7E?K74&?)4b~-uj34rKwb~7vU(48%+1xYc^VYn| zncI4NL8xEnmi>eM9EK&~si%*s|BX@zKIUU?cAWA5pdc`xEZIF1Ce=Wcg3#AP?N~p# zD7mfb{oR=ZPE^jgwD3G< z#8h1K&u&zKD4q*Pxt0ta#d}bm;QqZ!hFift22a~7c529SkmFQyN-*H zzQck2cL5iH2@d@Lhq4$~_!wMWL6(&mNq=7HhT}YYI$pVVZeQr>)4>qObE$PPNZ2!0 z&7?y_upwfiefj8-`B$ju)}QKTz*Zs<$Lb?XHBo(jyU(405&`EL({mgxA$Ov49U|rN z2@(l@n`1vzG(v=!u4AZ*0s}~H4{VgcNOJ1rB?Kg!=)mGHKWeC|MHb>aiQ4Qd+gq7|??WH7;?J+kYL8z# z@juTBhW#n3rN))N7T1~)qr~Es;2rln6_U>_Ejxj(E5%Cpoc^vfw64mua!ADSZ8i|+ zB}g?u(dtvesTegnG!9K33T)4eq>)>ZFp?L>R8Qp#(J=bxz2mscD;ZNoJB@ZUqPpI>o7VgScniW4c()#;@;-9PfR`b(r+#4c; z;1-)`!?b}4A3v^zVtGa(a;O%bzu(ZG;(l4+W^vU|a&n*xV0kU$uFQ!5!aWy)^q4^r zn!-6hfj79_B#>GGNvQiKMD?xyW>F&GS>3y?Ric*xp4cz3FH3Gd1z|e+Vuug7*Ya48 zL~K*l5zo1XRuWm%S~GzE4LQyuRsH1&L`Gz-%>!ZTYn9K_Ttz+Pa@9hKob^)gmLVN` zKJz}C50X$$>G1Q_p;%C}B?<9h`60%vwalt2*Ymd44dGF(oOa2mJQuPQmE~Yurn0UC z6(+5$posAd@e$nvJQFL^C~E0E4IH`B68)j#L_u|Ex5mNE8a8{>gAGcIFVS|K?g77# zE@R|9nR>Rw3(5}{d~HnPpooZ*XZC$5FYt20 z3Ydvy9t)XHw8qFCd;mt8r$e?RQ%MiUF@}!oDGG#E6xxV z=z>11f!msSqbAZYnSvt}&J+QXZCU5b`0!gi_R}Z@Qq2d2Mwc z%9aWfp&x2UGbLDvtjGb*p>4O(#}UE+QhYmf0&Vc_Ay<~3V0zym%`Lk}-3MOz<%)%#Pl z<=OjGrvuBq318+CJ-{30QA1-O@<-O!-zFNM^&wp}iWGG$B&eIYtF)Rs4;5FK=>Aa9 zyTJdUgpK$di~MI|ZC=Vkd^V6T5h^z))sl~Dq7~stg?&l_LW6N1>0nX=aS46Ks+vj7 zr#P2~h=M-LLX2!W_k&dv^Tm2}o9vK&uKMDMmPkEcj7~C78vw2XJx^s8uo(Lw>9ET2 zzXG^MDxZzwh4y=Hs@h^Y2$ntYP+GSm>#cM9ZiUR^>tiFtIol3wi8=y~L2f@Bun;{B zr@yZMir9Ur@yw@7ni+Jd*Oc9hFx zK$M%P9+XKj>`spPB?k6^h1pok(_k*E$fr(SnXlXEnE{ODRWuWqB2u+8*2z?-wl+WC zntSCtFwpr0nF!avN+7`^Pt@XDvec7%ipuHYXg%5TXDAXv;U-33A(vzDB8V%0%j-R@ zk!2mox%%pJ<_M$o0lf*YButy@IP%9Zz=UDDlr|NuSNW*bYB{&18Xj|$eVP~(lx>y3 zgjJh3l1)5_uw6CTgk`ABQVoCHT$nbFS*edKLAbhRxLyzMI-{#6H!q_O@+mM7#~@Kw zWFDq#m<+NGVr`grM*Mh=Dq@8Tzl-$WKFWsWruYa^v`B30wDORai8q&__SDBzc?K#o z^UN`hN&IN;bep+mS1Z}i#zurS+Vl`B&+6`B#XK@l^8+&2+e@&zII(kdzid}Lm^AE5 zqjZ+3N*0O?1%{glymHcUP?g3vB#mH9MA)__>pUakjX+4jPuRS$9mmbImM8^= zOGMzKSY0_htZs;&-)|di4DJjSjVQ}hf2vq`u?G4@2@M(y#8xp{#1&$)ZW$rlUwG%{ z-S3I$D5~^(7stnQ#qh(0D6TnSA5R2*0u@x*22u1y%V5wYfW$b@)H*9X9{5!1Gw0`$ z4^fR@T%cw74(zCoPNP98@iS+WaFoE>g!a7#s-iwfRHKJSou%<97*I%619(655MjTr z6;k$p>T1-|cb9V=`;0i>gjBf%t=3jn_oC874-1o3(J|G-g$c?a=wn!m?U?CAd4WKW zm>=k4ApUHFtra|}Wl_G|#Y@n(Qv*q-frfU@rg{K1dLr%5(jA(Als7lSt8bue+zbab zVF0VKb`8x4k`2s^D1=P<^mk&LXhA!1jsr46^sGC@bsZfT)hZq4gnT+I+aHp`_XRE{ zDgx9ExOOSGF^DuVB_iQ8s$S{7agA7rKLtYG0nVl0q1kdJPQ3g#tw9qL?gP!_e~V$R z7B*H7J0{kp*t0|SM#+|$l6`>>9*GXki2@B!1?#&`s}t$D9D05bdTLaq__DzJ3hhhx z4>Z*xjuhGkL>lPDr8KhXi~8N*3~eqgebLTG`3g)&9`ESMo4O`ywJ{RymGvLXG}!Y?yAZ!5^Y19ukC`n~3GM7)2v! zx|C7WvVV`|+~>K~FRJPdp3VTPY##;_7#_^stFuo>5ewhPn5=@ApsXs_<27I&gPv>g~?s5SHzci&*$xeFVsI6?MsNJwojSpg9-+xbDwNanO9CUPbs06^E~@ zW3}{)@boKx;MgISD4?gb;X2~Nzv6Vu z_d;=oiM*wq!ou(NN8Zrg1ZYYlE==ylKlarfHe9u21xL{BI8t!pRC1^0=DGRrV0_Q@ zC#L85xcROt(T$6-@Y|KI-@7cgFD>WF?-)WG5jRleK;pn&=Rb9nZ+_@Mx-Fk~VSb{E zq@Ay=ub)@s&Mz*$+FSlG0WrrMKZI+3YuZ5k`RZGGO+r;}6mJy$DM;>AadvNZ=5yf|1r(je z0NIXNIS||Cv*MHEs{?>y+_cZmakNb+;cq-QqDcP%tMf{NmoE%a zN}Y33Vukiwxzm0dhmNsZQ>TsfYfZ-XZJv?ZTQ(=j1nt6FMd#;_K1oqQ{yq$GC6%)U zZU3B>;dh0p{DE?0kaj|iKj8?vvgC|-pv7<_WZBV7+B?`x+~3_las0^52<3d}UOOFD z7O7yf($skvy4y{NCq)B!Z=x|~NnJN+V(IV6LPL~?ORfvDDj*}q67_9}bTd~ci zlKmqOV)pG2tgWwY4Xr65@I8rddMwBV71bVAeGxT?v8-f6l9tsu9MFYr4r+BQr%mT; zO=G1)NW}SP4_kI0273Ew)qtwOwo=X-`1?bJ^>I^-9FXhSX17W>;{G^F+<9U(<%-*JPc!x>jH zSpfzK?Tx3%`#8Qlql2)Lf)TAiKHBQ5IOieg6~2NY7g@9IFI!7$DETtUG^srTsi2YS zc$`cq59-bK0{Yv})|#O4%XrxCkS29A6q~iTWNRlF;SlDMr$~v5hgerQQg_UB>M>2% zI6J+NtM*`(N7ghI_emz^lYyF_O8LW&&6oX-gU1h39L7r@8tpHA@>FGx*W=fR6E@q@ zg{!zJeVuJaQCuA=1@IE7|3##J$1oumJ5vky^UJEjKU#$)KuHS7B;vs(wJ%$?>4zlr z<=b*ca@HsJ!Osy3xBOqrn__D7pqhw2^7;n0$R~Z;twx??hrssk#C1cMtRHfFzhTG1 zE{;!Tmiq;ZD9#2W4(M?+!*~v>l$%5;__SINKTNAEIBf46X8185dhp4TD9_K#gp?em zl9d>E%I2x(q#pB8rt!89i!Mi7sMMmaZ?N?eM2!JHoQ{QdAoSm@`@TtaEkw{)WuZe^ zzrVO3sL=ewi4YYv1t!gfQ_Xo()Is9PQtqh!#?v&Mscaiz6wb$F>GjZE1xw7d5)*24 zu~!(MAawsNH*G-kU-c=3l(?|JJl0^q#LV(WKmSHC=#5YKstmI(V=6c4>73kKDwk3F zD!sjK#(*WYb8j>uP??1gq4SEU63;>Pk_#yOYu7(GAy4!ABPQY-WoeY1I=l2&k9RM( z;&F-Ki}KoHAb;HXNP-^_3u`-L$+~dmP7LmypyE23q+IsyIAyGbu{1T^)Y7+m(;oN@;N26N#9X<& zwqI@>wi=7v)<%`#h|WWx1pPuT%3Hx zTmHj4u@(m6TMc`y;_9#P8As?uJeu-!|Lgzd>}uWMUo5{kA<)1ndxs@UZR32fT6pJHGaO!4QH(eAa5+t zS1N59EQ1r6i z<(E$QmAL~w+VkGpLI9*Hnm0tLT@_hjW9JWQXev%DVG3YZJ@}x78{*jc{asC?1L_)h zF^DC#%H`1`O_VrpaQ}@~&1zbs5~&ja^i#ZVXwP!}j8mnEV@;<{Ahw)4%S3LKNFJ3i zaiK4p7j50(Gg`7o7JU5p$cw9Ok3@$*lZ@g;nFZi|2gmE)4`U4Rnm2m{vKk-zbX%kA zCoK32`kIhZtyUTzRW&2mT0PG|s|zU{4QPllcC91scP>F97ZXap<9Bv#F$2P|qk;b&2$rxv~0fH76P8hs?SUZLs6n%pW)x z{94NZ^zuBrMOvmx1jBKr7I^C(e7yj;&kgD*7xRHBhV0n=;gNznW(J%ArEdQ3v2RnW zr(kstOqa&TJ`*F&kJM}we0``YRAQ>!`T?;}wzZgRk(fa^)#2*9%Z+psyrobKU%nac znGGN&)Npn`s=}e$R4yL6IsRDDSF=Ps)Z;1?NH}K#C*jVV4dx0@(DMhJqOL*I6)&L4 z9cLFcW!bbaiw~-ib4#2tjht6tOE}{zD6zU{xlC2$ zI>jGRD=rdrA25&Qq4jqQAhS4A^TEeuR}+ZLmIn&KRN3!3YkB-ej*-b9-c-AE)S%N> zf?x6evrm$2MOQ(b0-<^gvSC_6oBe@p+i`Ajxy1G91_dbm9z>* z`v6e3>~L1a-C*c2`$0^HXjr4(?IN{jFy+;}uvyb!LNh16HAJ)d@63e8GRMmWrMZ&F zv_aLU&4#ktx$@=QM^zZSdGAFn^&JpWIEc06k(WFQd*!&PpmY;wf3>)TvXQM+vqd#z zyU8VT;5@(~T!27u_1N3Z<{-f&SNd-M>^C*BK>cKP5&U7*KXmq@FP2FiN4aT+-1iF~ zfRiPbO{*ky%`uehvD+s~XnH7V{jvXcN8((ts-<3M-#N&I$MX3xlZ!UGg+fiN+}`r5 zkj3AjM%Sj6BRHE5?Q@(GmaEXx+0)r!TPtcgyrsy<^`_Wc*hwyr-;OCdQ4#vF=h5Xj!r_#p6O*Q* z)GM*S@GP^XHnavtL<^TD>&W%F)LS4nt}T73^w2{aE8S?2vByR~WOdM+N!yff<@?z8 zI#ww-Zu3B+Dw2VJIAV7nOX9!ujfO>l`;d|vXtw#0QXN#ak`$I0n8kN5(2;87J-CD? zHmL*sL>eCfe*GTXwvDI2D~K%nI37JKu}-!Po8ExO7L8{#pw*RuB`6KEDkQxqNdG4R zbz*yTL(6Iv2z+#WI#BgSE1!LJckdfI7H#~xxtSQ;JHtJbofI^}g8L7|Kn}2;V?6dd zK9bChE}t-w#v@|YYe!RB4PsH{@hW+RWHlR3f&YL23-N7 zB={^p7mTZ^ud}HaFV%4UvxHK!)luf%KBVaoi+}5rSQwa@bCw;vYHCGARWld==<7kL z=59v02kEeG3Rm_z)Zc3=MXmaA)I9-9T+O+St{6L3)`@2_41VCAA&8E3bj5sZx5x4s zmtI{uQpw=7HHzdjnUy|za5p(fC=*%NXWhuB(Dh_u6(6Y_e%!8tO&OI$^_@sEYZMc) z<_`+vf$U0(c!m5aMnvIZvM^uI5SEj)Z(;;xrCT_CmpZM4!RQ9UsISG;<-MiaiPA(v1+;q7waq z#DaO&yeXX-esRlYcP9QBezojM(;1VYYslzFHa5kqnhTql9tB)(1PR83ymJM)zr}u2 zA!bL-PF~HWs6_&|a2T`59w8gMCgzI0ZUSUfQfl;Ojkd&KMV<)NhcnfxuOH2mUXuwQ zAM*!OvW!{`MXjm7TIXfL-k+n%0dP~x1% zi$3~@96_CUQxT;Gzf^B~3kR0u=7eg2I4Fgw5M>k5m~x;XrP_^xUNLYFvz1}cRTX7r z0lHVaPz&tCq!B@(_+nwtq0RK$#IV+@P;sE{>RX8Bn-rrhrkj}46K*PBvhLdC@?i7h zJjx#Hk>f+3F<_Y0nGofcP^IE@)+(L~Q4*1fl-B_6231_D^dqI(^dhIc= z=LA*Dx+nYb(z7F472oY=W@o*6`ujtJZ|o#z!EAVr%)^Fux|HNxTtvhvDsp6UwTFwJ zM*F1zvWTTAmTD7v5DPy;dkkH$be+d!3z!mh9?~B zP;G9Vwc=}F40A(Sds~L)9PeFHO$%36su`>ADF4lttX|1!{}kJEkmfex*_yNVfSVdD*&UI|G|lX40rxwlAPgKpuk`23wH2sCfRuKK%fnp1R#=<@<9%+; zML4y^o|%u9_V0m5cLefgy9n<{uobfvYeu+aZKo0Ktc|gWw&pasMBNnfI2UHbKn{9O z)8)imqR}+@&r{T;xui0wrvTi{YW)CT-RWebe0G8{202Acf|Llgnqf=$=%XtXfK4Qv z=zT1j1nI9*CySKsm0?}}<#3SfXM2MsnAkgZs>SG?0o-+s-LK%L80d)#K;3u!6;8=5 zX@g4Fm=G<8m!gGW=R{0399feKC9Xe6!If(%Vf-@0mQ7tBX0NzqmY|9qPu^277yohID3?W6U;XA5NfW2T%outqW~PhQ+n&nro#DcM$Z$THW`N zvNBz|DwU7qm-tFK?Q`5dA&PTB@?7}m0eDq==POEw^{A`Fa?qK z&48UqJjKg|to+>?O{Xf0(K=JOzIa?8#vDp}6Rf^uG9;_RQ>Sv54OQdMjViE9g742S zMhS8Ye+*}NihDGfGuOzbNvx`CgC7KR%vHu{O-ehz$6LT4Mk3SiWVM?^5C{rNs<(ci zqw`nSS8I-1*=qA%mSmm%)UgQ`dsW)FynP!Cpz`|ATE_}k?|*Q37_<7=60FiHwB(_h zw5+MMx={v+RgSy*%jLa^{Rki@+7`oxIZt}@^zY`)n@lMhgAPv!!2u;Sa^;2L@?^x z%A-Mrjx%teimuzTAPSO;F~lr&gy>_G4IY{^P*NEOF|%r&ntw4|Ix}Z6Za4>|Vq}%A z6pcxIPQ@tDsnqjX?bEekhr8)RQoOi)#Gg%k8s-M;;psx6&rT16qf|d(x zQm|i=dq2&*4+`a7Tfs#LSH|);MEHt+!b{0d7;B0PK<1QGH_ynoq!E*2hGkz#6O9hV z?$@wob1i#9kmr+^>ORB=Br!O}1{@=Or zo%h~IPq;QRxJrZG=B=N=LCa3_ths#xboN?(E~BHD0#-A0HRWBd% zQcIeW%y@>zZ8l81ks#C7e+hpvP3-w#+7K8!Z#+falSF*kz#{e>Br}RGNxX7AU1lVi zBM!bs|1pEQkrg!e8V!3s{|$r6OO-b5{0em=IHTj>B%>xTM{2fQAz|zH#Py4>+?xni_0O!81gn!QL~C|A^iO>kV^4a_%tZvJM}($5)k4nG z1`n!DqAq7NrQbVbxd2VW=*}I~?A_RaioH~%?eBYLjJ5@FW1Pu+UAm(%H!%U>%pk7} zejlDzFG%i?NWK}?hzUWsKEW}sW!hRv85emvYXb>bj9PjkEJUSs#y-}~vu{`L=EN&3c~hF@`6?yd zt*{wD)SEe5tJzqXKE$Yy+1IchWywJgfw_Q4!wv!!5v&6E{)Mf7)=|Ty$5R8b@U^UT zH*#GGHSYPR@bGZ$75&;Bj!Dh8Z%`1MNltRwF(-lxD(>)-*7(HhmG5nQ+i+Z`;k`|g z%h9)2??XolklwMj)H3$J>HaS9heUSwj9nb|SnvxxR~23MWzjJ&wWNu0GHR|_`D@uU zJcWrzlRcU6ndDlgFI8Lbxu<+@@QxstO@yNH$yd+_nh{q=e4eP<==cK*H3z8Y(t_9COqt4~v_Qlm%pPjo%wZFKfn|@@9(-C_ zTK~A)tQ3f~*E*=hg0)-;lGt;ScvIjOMibwZ4x zJ_UAlwx$oR%6XV>upP2|637WYo24&Q}Y_fL*yf-Q)J=sU0Ln?t+}=J zO{6MCeh7$_?fo>?^zii23s=e9C&jWN+3Wk&N8il?$Rn1TVg8b_3$+-c4t1EpM3jNP1tx-~ZtZSw|kM3YHhY<3yn%Vn1xhDJu% z4Dv4H$I&nplNH^mY?|6wy=hopGrWsK{z&zWzg~2L(?_BXd*1qJV>321H#9~{E*{+K z!e9TFLZas6aujoB{o2~V*B17dvd{&Iqsk3=Epw1yoDK19=8B`6=j}^sM*D%B$mSlQ zX#nr4DX~ji#!=Nj_)ias_^{Y(lA?qcE`a>{=4^TOc?#56oiVbq2ANi8i&=TNn?&pk zt`VtbWh*T;WGoa9?%8a=={cj52ay?-Yi9r)62hP4b&xzbC(HecT>GQPlc<;0Z%*7x zZodr#pCg`OB3`dw!hrntXAoJmo=QMs$@kx$r(LhAPd=epl?(E@ zTyv?TwckxHOeIZy3=>WJv}?OuzDp~badvrF4_ zZAYU~d}%i=v{4M&=+*K|6X*V2+1Qvjc2Ko9YD}ENS~}lpu>xTCv^#n6e-9qt zhV_&E$RMR>%`RQ@$54%E!G$j!61RAW5b~GSPP)}#v)oupgLY4;dEuZK@1+Gg;XV}I$rIL*jyWr z%#b+Fa2-|41c5tm(GN?a8dVl1zFisqiPky)WPO?`%oSsK(Hf&IDaL(r`%S z-2Wn#BoRnHfqGV*!s*;zG-l;5+rkmw$u*-sA!lNdlNI=^8=bE^h^& zEODXG-PWduHouXLwjF4F!(35IXa!Q$a@o0)hwQe^4f(f-JAX*4-Cow;VDb*TZdS@H zqUd9T*+%su%e6L7M5t%M=UJ7V9HyWKQT0MWs3COo66`!uFnY3gmQjYiy2x8XhO@)> z$~WPw(}UW1aF~-s=CIaPH+8kG4exyi}ai$+h{shB*3W0rRF7=mD$#s zvR#Q@SDXD3D^=`Ph`BRQ^{vl_$cFGe&)d~zCy%|q@PdImLSty)@pAQ1>&enPc=}Hc zxK|095i`i|VQrKL0815&JK&dK9DdZJTv=}cxe}!(rRTVQA zz>Br`kSb^ePLUvOWki3xxKlM4deNqbyEV}je3vb|B;s5&FGql9?_#CDoYdH0y-F&x zmmEfNh6h@>F{QJ{ho4NR2lD=9hGNH2oIC_rb$IML zpQS^1(_7Yop5+Vhy%+YHF|E`%=bc9rjv2?=;WM~G<|FyL6?u#%TieI6z;E_?35N=+ z0Ixo25mhW*iKUS!M5jj`B4Aoh4{hmH(BZwuOSArZaffRMr0bkL=(zyx)q{3nGIFCt zP?|CQYOzYk5rJl?01bIJjV$ahRJVSWd3!3Z>FXU+^up2{FBnzM>P|-;XGsVkL5`RF z^7=C zeC2+{=kIBc)0DD5`G_YoUabnci0OMA>;XphacRZ#+lS*D8?ARGW7fDCOLMwkx#)by zx#YDL*_I7FjrWyjTBGud;0GL)qpsT(*rB1J-_=`Uw&ydA;1-mYlcj^y@4#eC#Oae{ zJMzbmnKyLiYBU&+6!x)+AHU8|r(4I|5gXO|yvLXkB8XQ!H zX2baRkI_{jpLFvC2dRbFcD)-@6RwWk6)$7O2aHGPQ4w5Ljz{X^ANl66!{l)US^OWr z7AZob!By7dm7H-cRkSe7adHaySI*vu#vJk0AzD%0Oj~;1NL0@B4>hMui3vafOxJH( z4|j*!N321k^8ELv`Q|voWIy=68f3oF19ight;SN>tLXSx=j7MN<#sD^G zXN=O6OXa?}ym}R~{&5qmA3br7O-gH%p>*6pf0>seX8#r;TT_si#b~RwReA-by-m5@KaM)U^CF;34yDGKb(cEIZa6%3o05E4cb7* z+;9{Ba~%6OZ?QP*qY4Lw{;`lW{Fw2)eDG(3ZA~DV=!e=H;w!?-D#OdFS1(gG zyzFg7o63quNB{kdv#R(Yms~Bi4g9(oQwOYZYF`fcDwZ;-e&+u6T3W7QyfyOLH~hV{ zcv{U@RWmFQUhZo-NV~bPb^B)Ma;IYLenRx_^`LpLomh?w_P?t)9#vU4oFt$%US2J7 zG3u77_b6!)XWOBm!OJr?p02gOc^iVO`vx^92i{QobuWO~{!bcylk#?ZolipoAuKZr5iYfc{YDSBTuZQWm0!K#TmjNYXzrs)cQG&h zs{O^UW3-$Pb6!s4t@cgj;iXW3B7S7t=z3bJhFpwR45Ez8fI41>sx74>ekw!_IkXfy zaL5ml)#=(w-DYW8AfCLQ1e{;|xE}b|M;gTf5I`}KA*Be@mJHPc`IVnmN zKzM}j2YhkQ(rua?wS`rnM9N_)A*)+I#aruc65|6j1X`K72zoM*5Z~k)`YpJg5u#T# z1UnK~t?@aOUqv`d{*9m0_V4EBFisI{SFXLr&WLI~tQ zdF3Fs&^^1nyLsQF`roY8z^SLRWCE{Et)_#r$;h|s@RR6~(s*+?KO^%8-RISZ$H2>s zU{yd|BIT`kpIB5PjcsOqU)MkLBt+l-ru8wdyMpf~uKXlS!ZkG8fCc|ZBT$+q#M{LXUTT@!$(pFyi+Z!=WrIl!ht(fbk6;GJYVD*)Qw*}LClLT+2yS_;POgF zq9xDxnSU7MfAAHf5i3~pi3m+?P6Eyb=Wi3&phKKk`PYcAC-FI3!sn7~p9jc`Cj$Q8 zuHDipWtBYU8|yeb(Ipdt&#=;h?}Loqf`0}UBZ!p$r;RqQfsXP)&wO+4Vflp$K6?&Q z;twAQ9bh;;J&DQ?%~cJxeA4^Usg3;(?o`E|Mm8(tG|Ayr6JOM1hW!Z zqxD=krm74NT!{cb)MHL-r<17RXDy8XM(g;r)EeD?j?WYa&0OkUiQjcxzi13nL8K!H zeDiiC=kH~xEt7u3fCSK42D#NOh42IayWdgWtoKjlQnwdQM6un!^>Q};JNS3NxvanR zz__R3*d{xY)ysy%#g0*R>YHm?_pI#R?Qj044R??sFMD2~Kf4zvu{NBA_$usENKfTS z4Gaw@rs*oK9f_aLy@FV(2ZI);S8rim-Z8N3*Dz@+q80$8+CUpR`}czcAl9#Nm*w` z3|4wuio*VcAN5^%L%@{ESF$qq8bp%5q0YxJqK_}=U17JDLBB@&VnLzg8n{M7<51&(7bIU0jO&t zore{7s{$>&?z~!j{}cowSNOHUwt9R85(Umm&g{Vt?c}9`e7nV{JA^-{`()zWc}mP< z`6vz@TnCDyM`=+5RT8M76SsxK1reI)_I0bypU)^%KHehFfB%DUBrq5-5*yhuSmA{K zg;^?iEVP{?k%jiZ^P{_rUv90*a`V}0T|DlP7nH#NEk?)g@D!tQ88(Hzh=ZT!Ipr*U z`$%5ehv&a@uTgn1q`VV-gj@&HX?$b+@rmi(FbA5?fQfs@S1S0_0zft0jJDHE{%Koh zJ}Yt3x&j;YrLThxA1C?y%Im9L>9sWfg@~pxH)IpP6d7j^Rp84-`?w#;l8_>mLOU$b zsHSafe6DIKD~U7^dD|Fa5hAcEABzc6^Ktz%I<)h8d7rUL$;n|Or^b9< zreSTSTbv4S4e zb+4F~=Rivm>wW8;?bgzr-caIP$LEvo{?<~D?wb*f zZzmBM!r>(u$Kar};P##{zdSDu1fuBpt zTQBv*X8N3?HakuultkMtd4Q8C_V4LnBc ze2rw!s6?G6Uf98Phn-$ud5-UQXr(!yslCjt!C&F2N z42*250>QOtI?~TE?4s8%=3ts;Mezd=8L2BMI?lDT` zd+-%YaKTWgiUykY6;X$SH8WzJweL&qkIL~-{r2?12=un^tCjyE$j^eWlG=R)b31$4 zkO%>Vx<_(5UEW5hTP8D@Bgr(i{ZlwprU{UL2MxN=FqS}t>rLg&(9wFi5&|a?mrz&# zoRbHGs<#$=Op@a|-xV_Vm;kCqZ$2nWvjFWH`@0g7A6!LRVAWKP@LcmdKUJmGD^juJxC{MLX2GZvG;>X!!?68TZ^|$=XepiPnI_ zw7cM~+XO<*d*G+10HH=PNat07nZYlXwM@rPmO7qLXF!Qson(VS$82|Sra<}4PZMZ7c8b7fmPo~Zh5UZ z8?C7AAgO@JmB^Lw$JuK7FPee+iUh%!WLW-D7|TxUKs2)mc23L(zxnOpF{>7~e|-~t zbXysjma)vW3S8&i124Twu-3@uWC36HbFS0tID++G@BkdO@4}9WIp8^;aod!0VE$I4 z5;fO>p#q#OGeyM@^ah^>oA=vc>$sD!WAYKOo00&|IytaQ`xdy*D`N*(3eq_ZuzOw$ zIBQjakA4H}(SHCUoigxU#Jzd`lQpGIf8|7aJx@rPiiDYsd|b{%#vtYR4|TP4qD1Ui#tqq>Y+bmSmg z+z30qxeji#D!^@KHArVQG7@eAhbcu6u%r+A~fUC79DP7T;iz6qqP>aA;GauX-0lUmB1ZVAH z_OsO>oKgUmQ;vh}^my3zVKK~m?Sv9DSJi{!$pfW;*{indelQza2iBidfaQ!sAexo| zPK*$(r)0pcX@wB7vWcC5TJYAZW`DlNGS@ng&Z~hyBLySeI*x!{=iCE7!y4GTv>AMt zmVuXk1^f9L2wK_(A#2#*o0AMKbJJ1-)?5j{o7qg$W{F&hT>Bxi_OzG<&uGuwKfjIf z$8B($p21eRx!}LF0QN3t8K+Sl1g>acoYKfv&v!w}2zD;Lm^6TFX*IadD*~B*3&<8Iz)iOh_N{4x&{fS4xV()0>{SrXIL-de)42zC zT=V_D`JV&mh9hz%a_#%5IRC#BbG?4r5j;ncCegYJHs2kk*xSgs93s}2gYC39u$_8}eepBkHv2-_F}GWG%{AYX9!um( z774GGer*__v8MIZZRi0t{)o=TgM;mtgF{f1@A>Sz*Fx&rV%=tyvBa#2@k$NsUcfkLVHNCNR0SThtHEXFUGQ5}559VhEa7VgnO+;XOl8R) z%Wx(0a#?bB4$McCF=BOQNu+&*GB>nFO;-tl$tt@+bD%d&8R!Sg)$+h*Oc|`77zD05 z=fG#tCGgZOV8n^t5G*xc(g?vTo4GIKKD&%d**)j7>{Y)Q0*q_GcafZ(glY&jsRQqM z)!@Cj7`$|=A!5S=kQ&?p|CQIkb#@k5Pf7rLmK{rG+yvJdSHROK^H{-|CMw+`awT%@ zBWQ2>Wx)0DUyZXwKRL#4{2rn<7lEzz2@uW50;g%|u<6SquzBoJ5PTL4Zu7EX_mb-@ zfvaYuSP3C3Tfl2!IUHQq%CcF;D@!W5l`_f#vPDg>Tfd4+@?2)!WB*nO$4%~YO1av6 z|HX`-3`$wndx0f!=eQ=RDFbDU<8}*PQf5q6@yebw(48^63up|Kz{1zkz~Y^H*g5$u ztp3awJmzJAXjTqe?pLw{ui~l#b}z)Ge=+P?S`TjX3&C;5ZT98Z7uKs|%l{TQAW*QA zQ3{?5%D|nyrS`97ZxzETkSr(!kA;`ObzTN+85<27zl>zr@nNvlJPndr*BOalJbldW zu6yaFmM`e$BoKNp?wt8yTI}ZU_T=vV6@1xJ-`n6Sm`~adn_P~fyN+s9%uO*1JRQwsS zy2CV;K){ZzwL=TRdSV_|>*_e|G@89Q9&<}rdS3$v);7U@(+ZF+$p?GQR9N%L0dSh0 z4i*|mVaMbcu$dAM`_~jgqII+MPTY@kTN}S4J(fV|O~%z{ny00>v^pL$ZwolGwgY^% z8$dj*7|f>zGtxW@J2ayi+2+IMua3g{&%;@gbp!&J-GZ>yb&OL=S!PosuYp}vM#mDC8kv z={xzL#a84DIWH+YwACWibOs&j&=}|mlLzjGDJs6O;`J-A>x(9^(`HL|ta0Y3WG?Dr4Y$zkNVR1QH)TfuKp4eVoC>%nyj zmd!RpuyGR{SXU3nEf_IRJqs2SPO_651J;w0!C`tTh-RmOn?Wkei0?p>umO%+)p+L} zRT#9^|D-}UE`h*b)D(8Sm*HPyeqc>Wc+`d_aQ?g*Hmg^{mJjd3?!|Xt-w>+`8rkakE=YB&z+1l(r1Pu5XUQGz-?bWl8CI%Y<5uLF1N{Uq z^+f2X9JJI?J;Y_Ls7=fnbQG-LYhugy3t&GbnH^+2OSN-BGQWhqL9isEhGn1C?29rY zHDsi^t_^}$H$a4W3xus}VSjFffK_tvSyT?eYpPkwUkSbjmF%Qd!#?(Nht`*a``k>h zo0I`A)3aF?n+|3Z!eFP?aR^va0It(2!SS~famu?$wP99*>Tv!5>mAH8~(xn2clZT5LzmBLKbNSHi8lK4_j##EKS?8yVYQS@cx z8UtI@8(BJk58QM!VB7c@Muu6O*MO&P8OuPM*&BjouZD8i%ib`7#?`Qwy-oHQGcsMt zvRn3630P6XveibAu~hwlNjvx%RKf10g>Z093&d_G9T$tvD*Eta`X zRSAG)ujj(Hj|xFF?+kd(y9{o#&w+Se9(XLg12QAbLTe#JAO|n@wg@s|>HNkPh}iHQ z_%APmgY3kFnKi=E9c>V{z6rb+-G{I>55U{75JJ|<*$FIV+3g*$7=Ik>7`g5oe+F#7 zP2)5YYwZ}=FDQi_U)%+UcOHOX=zS2pQ4YIjH^I?O3fQ+)9(ygaV=3L-1VYc?{^iCm z4sE+B+h=k+9B1z>`!F1|RS$si>-lUMUceHwIWJ|MP(pmNnGffMmQ*Fhmh6v5VEQX{Fbt; zl##Fh@(M<}b=>MXbWH;U88t$vaT`cMaayu1HPo zl;i_Y(DA`h$D1ypD{me?wBar+dp{B;4R8k?)o{=q6wi{NYA{i|3zowhz;0v{h{v{q zNcSQLXU4tDCu%@Zl}3 zj3XLguW==W7`HI;t>@}peU=t;yc1^H0=v|NatLE2(x0wA(h~} z^ghQIK`ZMZa2fk`c|H4mEd;V|-RlcWEtq zTQozcNi9Tfd;k#}+Zftm?{Yb(vmW3269lfR1liJ32wqbLksBT`(yd`{mPR47L&PmDOIx~kY4K6{@vN{ld!#?}nA7SgTa`sj%0+ZM8 zv5R;X=BUPij>Ic;2MIby!)824qAEbuy95) zXulzaZ(g;5X#)dU*6POX(M(qjWzT0NtWqmvxB*+$tHI{I1_(541vlL+u+%&TYrYJE z9TVfhW7ZXLoR$vTzfS!B*?SM5s+P4~ch_HMF9RwFm=o$+>e6KnC?YvXFs-%se{Q|^8|^-)>fZYAxqsSwuQ0o+Yfi=-a{^;_ zzx}*lf87HKx_3})+mEaxy~wugWzd#r^on$%pY&u5`8Gqypkuj5N0DaSPa;Y#S^Fi+ z3W(HviA*zY)h9un-fI%^cPKeNgb=yTo&?n%xj+5di@w0EAg7f*2vfNMpS>60E7^iX zy+@2*Q}l;%+GZT5k4+-O^gSZ!c!AXz@~jB$P5an|NHuwl)7BqQ;xNrHpL;F!P%m-EKEeG>UE;$`*4-3ZLLnd!@JcCukz}DunxbU;%kiV zJrSwhQWdXz1N(o7VFJ42I}Z|69|kj9zjMMadd@9AlAVdHW7I5Bq5#jQ;5vzFvr_8vpA`z&0FY+u$3CaeLZSfvC zM+n^P`;nmEjU;aI(UCzC(>|PW7-7yh!;G8c8ep;3Q)Z(`IsA4qT(8UgPrua?q|{&@ zEPJzui@nAkxJm!;019nB(8w`BLfOZH&m5t0G1e^l=Sxpa;jH5*&e}|o;0_V3zDJek zr*9XIaKF@PjD+_Uk~JU0N8$=R_B7-8)+z)@cfeb=0rC59BSEVVfg2{^vT%&Z^&u?h z_rQq%J~ZcCgx1_3QKS1hD116WILSaY)RFX8mpVcL8iCy&Xia+-`atxth&? zLFD=dCxl1fw7eUM>YS~A1#bc+FR6NjD7C?PcO6`I)xr9w5+v)~NB+?lNIpp7YSNEF z>v0qxpC)Y>L8{?<6rC7D43RIFZIo@^hg>4md`nJDhnX8rHtgYC^JI+v)1VqB2>j`{ zUV^sW7YJ5t4T{majRGznLiV2{(cEK$EEJG__#LuLhfwS|fl?CM94q?S;w{dc7-6sH zSq{?$A0#2}qvLN-e1Z!T+(v{-7yPBJ!%wOe-qM%p%V{JPMZ|U%_c%FB}&1 z!&2}S)ovOkTUl~2w+}6sHYPqZl15c8HghRS0=wfoPaIxf27kF5aFQtPED3q+@nP@_ zZz(OW^6I})uUGY``0cAb=PFy;>Lq^;G6Eq)roOCC{q$!$Y@gwdT{C=1SVO39xwE?K zJ3mITTtC$3?}P#WHI{;9E8Gje??;F#2a#ra2Y!1m!$GtHZW8BN*e^)tCQfXtK@sUf z?vXdhGJlJ_W1NQcp}=+sXNgYpkB%YFx}P*=l3)_jb_wjZZ$N84(g zeir%D@2#{(KqSv{pdjf`H;p<2$h90~IA7^Lg?y_K78c;dw8V7`7kqv}h5HzaY)4S- zJwc<-2x`5)&?xl*70#nLZP88k|1KQ2*O9n(z-`ZE1S+&3P^lRyMo*EhF$K?6LvUKq zha-Y7a9H3W^yjs+g$~lQQdoFEj6{~Zn*z58f*Vc6W^f~}2lg$>#esDxY&~)QVFMU9k!Jcgg~lo1wBajQWi$392o&(IXdQEtOh%osZ$TfdLBHDu@>j@S|AHz%Z3cU8Tv8Avl74E}BvL2_bA0tU?5Z-GCVK4lS z<-D5AzXP3l%~0hlCrXW`8p|qYSGf4kZW?j9y&JioxkkXnizMdx!E*CyBp-N)Gp?^A zZeD!D+uD#<|FCte|I@6qUQdD(_TMK_y#oF9ao9P-8(U{Mv)!Y(y7kXa*!mqOpeOPD z|2XjN_)I?*ca@qE#~dSDDnGjfM*I(PRIrBtXb2}3_9I?-nDpQ|eB~~|RxA%T+ltww zwVP-o{KRg+Pr4aJR^2GJ??WNcYNmM)k?R1m&H9mVJ&e4gBLrikD03yva2`YcF><&D z1Cv$WlTLs7qm|ra{pQ8TCwel>-Xg)^InqqHT(nW-+r1-vA0)A*3*|C_QujfWoR~l% z;eIiVN;MwSM6W~0F@6oZ&6V&LZ%3$n7d#|rgcGko-2NMgP<;*mpN8PIWD2%I-;$IK z`ENsgPA$u?6PpqCO+aUId3P~PV7XD2YXssmBA5Vk!FW*;+e2&f5vbZgcI0hVvHSDz z{s+IT;&nD&{iD>0v5)`KakftHnAnaI=uJ7&6J*Gz(snIYIY(~DJZ z5^L*s&P20b*h1%Uiv{*@uXE{FGXhztfCHPovvZ(5w~=7yCai^@!DZnPyw?vPQLmrv zC%|nd%B{e3qkiosO3$TlAyBp*sRwVP*zpxIEnlL{X#zE#pOJ4lOcXneT#F$R*Vm}< zqUScqv-e` z%ALkh>NJ2_mm#Fm4pGVv;3{4RFWEY>1aA>0{T^=1`*2v`4hic`m~LP;)3<2AAMZoPkykwxZa>TM)b#(Oq?z=XSGs)cDY6?wDOrDRLaV}M6a{uYD03ab zS*Ly?*g;ggllZ!gBGcd%0wiw1aVJ>^>1*(oYC?c)8&XZlQYiMqf898o7xt3{c>puA zA$oJ$**(9wbUB@qa8E2+*V)qoFmqqM66ueBR8kPIYW)P=W&4l8cYdx zP6+qIZOIT~l*W*5!rddQ8IGbAu-$nUo}$fg+1?E2?M;Z&xQDaWZ;@m14#f_`k~>HM<>tuO$W6mK!B&9|Blk=|5v9<=Z`&Q_LHdg;)2rysBoSjitRy-$0W`= zzQ;xXG31%NMyUK91WP=mFQW|}VvUGUe1I&=yGYW1i@?nja9lXRtcMX1tl|9YP@H`l zDtx6xsu}Dq3R1IU*`vaoEV3+F)Hpm@I6#gsm1-slZ5*5YQsB#F;R10Qouy`S?@5ID zrXr*oJ;p_sPZ4#2<35A0KMM0YDX;z(Yg68P18=3~Mw{)mIIuPg67zhqWrjT@=7g|# z>aLkS*iCgid+r5^*^zAWN_=J*#AXN5InL~L>A&5fWGBlZk0kdO%*d4s#c^3WYI7=K zA=pd8Is~VMJqTVuf<*2nfd{(~CVvY-vbR{ydVtJzSZ+LvK5*wvIt@fM zrS)12zn|peby!~gP23IO-lx??)*q4s74Ka3lx~6f>iTc_sk3~ja*zIyntKx4W;hYS zx>I{6H%EZ+(|0x`s6?@R0W2)QCbmdyxv&5ibL9k<>sR9B_&CAkZkr;{m(9eL+v%TM z@@gym9zGlTk;>f$>hKe|iPs}V;|)&iu7KOFD>$*`0wU#}A>ZN!F8B_k+IIkD!X z#@jN?pYuWh|J8CoA0kyA!)@ixBe)##5p8k5px*Bbs@#Xr;5+&^aeV-n-3{;*Yi3_e zIJa}o(RWBv8-nO2%L-zkIN?dw->U@4S=c(d< zbE)(CY+mI)-cxAbgEF^%BH1xC_>Un`^AY?cI^npj9$pen@Yr(&?oxHgws?%x{iE>v zVU$M5XE2$6m&IOn=3Rp3ybJ7$-a9Ls=rsT;^9sr4L@+DEG6-h)KxTFlqg!r87nl30 z$d~&qR4_Y*H5i#WTnbk*l=!o$;dwE-zjznR9Pr%J20t48(v0pRVgGBy z?3#k@qDMF;^csf*?!rKzlj?P-&M9Fc%84SEHo~nO;cN>RfBlvN8_DuqcQT=k$6lgS zZgPtwRT(~_T)r6Wq>)^7*0-ELMzgcSuwS?l#}+)Hzvm@RYP2I%qn6SpOp09e`%qBrIz;yW8DdnPBShv7+;%syow6boA0k=r2?~z&Ax35b zp=-Y2m|!eT)pMu zrPS9JqwhcR;<3E?53LWc_iXf0ZK^M_8cqw5y9w=udC(JRf%?2MYQu3jxS$15+SlMM zc^g{%wbbULAwJKKg#~ua@?=80W2P&1&T@z3oKULYh<59YZ^yTP=fWm>C8=+4E3&x0 z!Q36WzyIX`xk+Sh+fP0ICRhkQh2z3r_-=WJ48s9rnLLA=< z*Xeon?_J-%8WavQt2w2#+-t~gdjlNB>qsb%LvBtIOqSe)@?2{BWZ@k)JV2hs3wV*Z z%FRuNq<|k}_(R!b6_-*aKQ9HlXZuj~BC&PHZa#PHne9u|>I><45%k=Tfrb>{$-hBI z9Lv7pM3n;;4o=kOl|xsc9)|_)v$RNuMQ;!+(T7~iK6aOAZWpXj`CIUn?3nZxZFSR-cP2$@68=YsvI;D0{w>EiMRz{M;1C z^QU0zOnVa9lThSO!y(~j78)=Tyic~ukKUKWNLg!nDgu=*AzZ7mChJ&NTIac!3Oo_u z)xSs03vKn#Tov|SdATR-cAbIdl2m9c%76sF7c_*5p(AvWxh-{pBE%?UAp)8Qa(z6t( zFK}5lGP4ueq%W6KzL)xo`n*c$^IwB5|0UQ6_rQPkDAF`PpxkK)soLG}mZIa^N`mAB zoOp57Ut0;<)*}!l_d3W=>MDHpbi!5a0>ZT~Am<&-YN3?2! zc_hH!LI-klH{Fzp3Xg7_wS9}jYb%&w%JE0B39JK)>ZqMZ!brFi z@tUuYsPPth!sj4HA}S*gitT)MM5r!M6;6k&z)2{~r}jNJjE=ct*KBueo@vEGV%%hw zvcM_q;q#`?i(zvR9F(wyIOO!W%7q5B1kS-s_#Tc4y`cIEUh9UCa$pFjtRBEes;MpC zaEKRI{nam}m3uDYw)=8{pF}&Nw6CJfVG2<)18`qDf+Ki_%EeK8r*& zi>Ni7&2Dn3S5kbD*e6)Ph*f%SB#Wc&nc+{PaR|{Yjrt4oNnAr%I6#3vmCcMw&k2Vp zpFdRQXG29W8`|^F!FJJeSS+~@t@$-jqETI${}hpNGE{^zpeRUUyCfd=d&-b*dKcdE zHO(a_Z#a+iP4PsQSN~J>_SI+Goz?R%>a2==Z?mHm5o)(letZD+zT-&L?1RdJ6zt@4 zf&#TYZNVC-2^2zZUK}iz-XVAQ0`WSJVX(NK03Zf(LLnrm^|w|$_O$Ax?tj!%Y(Ic(-7oN1(+|f5BQ$EhgrQI?bOr07 zKED_W0?G9FZGTs8a!Yn@JPQ$Uiv?unMl-SHVpOX9IYg_WbSxH1H1caMEQF@eSrXP* zSgg7Ub-{cVCQzE6O3w>mBzOxJ3m+5J=F`ZYgS~T;sbL1N_bQSos|cq;RKN)`!hWz9 ztw6NyRm7XL3LyHa7E{OLx%q(k*zPb&vJys+#nL*a3bLdBHC~Lg0*qJQ0Cyci7qj2?qYTdl;;&< zztCkI7V3iif;Vtl@_sU8S3fVV`kP(jX@oid}rpkl^=$ z;krz?%9bNu_hv=vk_D(i($6Bi@7MZ`FV&`>O+>%bGZKWnzczOfk14TX^Wk6 z9NC`6asts%m>&z#dG6F+!yrD_2jYBwP!ddr)Vx5JJs>{k+oRs%3O4V+Wz=wcbnKkz z0mV5vP@Q)chlFpynuOI<@NQy|2ye;i@1~TPLnL6^+XD9`lVsOlkv+MEgY!F}KChgJ zw1_Nw9*JirON!=bRDFICTO1%sqqExl( zL1#qaB zpwd_Qy-l|o@r7!-x0u}?T3=BwJ-X7Gl~ zE+Nl!5M_2F(57>?@!1lM20?1RHzfJJAuZ@f?K23{0>KcQ=SkG+OFsu=>nt0hRewgV zoUn3X16lqU)*sXab69RTN3GmEg#v$8kB-0vUR?E$Qgj3^n;S2^+H+t*6AmqHf#}R& z$nvF-rHRD81vyZfpH8E1I;8nxAU->otW*inY(5EO0yU~2Xf7;(I-SSmx603tV|jku z`y}TDu+d#fD3MJLSS@}5GvSBO5I#ennMR~rMvc1wYQmW$tiI4(mJZd0Tzo4W@(aRP z)m)kdr9~&9x;Pe!ivw{&{4CsLOIyPYE*9Ua$mQeoRbv&2@yNfDd-ec4Q#~ z(YfxdjVlVpvQUBS+!!|D^=*#gB%4=I7tEQIm>m%$ClJI70sIk*fpBZk!9|yQSRj6O zDE0{!u~ZTz!8Ee+1vK&okSG#i&Iy2uP&zx#k*BIqCX3U`%!{P+a-g%Y90n`OS-J{m zmn7!;lkGYOvn4lRvGg9ah+GdYJI_*Jl!Y>&ESyXYof_c6R3g?;77mahN-$V`8ZyE@ zP+1ZM)umC;SWHyBA{oY;GGVki2FJznZ+fT~T^#5c<89FW2dRb8S5BC0Pq}wwQz5K( z6(RM&3)Fi~pe1Aq^+7|p6gGu(Uejz7=}M=sM6uIIQ0_*Z=M?IEh7qv0mBsWW1l?Kt zG+EKc#E^r5AhEYd)p?0P@t4%5v!NgqNzN&l2KxvoFNlZE@>48pU>6^^aKMd`ujm|4 z0)TXu_sT6IP^EsMFh3sqmy|(8Fat^g1Pp@N`EmjYJW>6lmu)k>L=@&F6sS?-(pqo^ za&r>N;uo=5PZ|C&i1P)q6)IdKQ(KS)**P)va}o;?=q;>d@l)+ZMNE9PmgKMr0JVi_ zEM@D+lKZe;{usK#)ht%ag%0!=*FtaU8K^Euh78#)xdnl27WdHFLZ}g~sxKyzT|ktv zG!Y65=x-46!GX0T=8Hn0yxg1JmDWl8Y-d5xRj&^NUuN+H=y$qgwWDvVyYjh4gCCN+ zjn`$tWm^*>Rqmn6VF;IfKjKRC2Q)>Dp&{TS>ioZ=<$+j37ZJ7+A!?Kp3P20wFFyVl5a0-Q@*rgBO+gS=cheu5H&$KVArcSN`83 z>m;&QApZWog`7afu!R8{3ksmWw2}q(rRS13F3g4e{8*w{YIt-GH<`szuh!yxYIq!x zCPIZoQ(|r)S+N`(THFH1HE*H2s1jNvw%ob%;j63u^vasu`!sft!D$d z%92PDSYH~@1DJp+2~%5NK$N?b+USyW?4IKcjYTA~i&LPoFqYmE!QeuAZusPGJ|An(yUL=us0oMYf+B4_PU0;%V1x53)o)ECowrNd`+>QC*l0MS&C|f=U>z zswF|qhV1-sXp`6)uc?9QifcHr>Mf3~d<0E8CdVJcLJ6FWGFV+mjg!bgAOLd0L<}NX zFyB}Pjpg(jk%r;gd?JVt9NkzAll4W=6-mXxwYgATMg+Yq5(j@shyMCdm~Tye5U6#& zrn%yQ8c&>l+qF4s+$37_RZW=kLnNpUB2lRqQL@hwEB6L@h65qrc#y z-zd&|d_twm2b{5*Mve0ql-m!Z;LrftB0l1j(QBBktA(_%7bN&SVY{IV#!FkEyQByw z)^_8R;d`X(z9Ru{hW7F_Cahxf+;QmpGdQrS0DA?)Aw}e>ydVxTf&l~#evn@n3Q7I| zBGz0ky=zipo?noTNIowFz$^d$VzusS5VzD%V{s-_g;QC|2^TsrTvC7iONm_5ptrmTh9YHbWy}5*r=h+e8*V?mhw~4;Fj#t?&W(YxU#2G!xsSYp%n1aXak3e+VOy^DtOeNewv*`)}@g+hrxJL5=?$dhT+Ee=SglC!iRb$c_RBOuYHd`t*CSwi7K$@&dNFR z90`i=5ib6SNVNx%k}r`c-_JxgOLqXp#|BaBI)LWzF*Jnrk+^FJ`I=GKzDHwIPuk5l1Fyy42fzcWckC%_MgSkbuBo$;xSy;_u}yC z258ec2bPz^YQt5?3x~7DtG_ZIN{hp&hT`a^D#$PPV|1#%A_6MQsBwRv4ZE#%B(gbB zrJt3T2E%mYX&l>93H8;1&{!FbeJdhi@?$QHf6T<8^~um#8w&fqIn8Y)uX(qc`8B3i z4Sbq)HD&B*(b0Dq*$3a?ockDZ4BsI^;T__n-y>S`4I)WYW2Ac!A@vNo2ZvDOGJw{Q zk7y)XZ9VxB&5_e+4E%~3x6i0N{uyOfUs31#85LF^Q13B~O1lX-h}L6|fCEdT;s$)X zjklq*q=?#JB?^wx?78kn$u+ab096`1t}qKBG+_sVX2cU z!g0JMtGx2}De^+m=0vVNN`i?nSXB!Bg9W~@+)~EuKNljq~=w5AAJD-#mUd2v-<`A1|Gs4q?m(pZ{?L#xVhaAg@(7bd`RT@#D9 zaJ^g zn+tGkTQO{QmB4s?9(Ak`=zkvz&D8<#GQ69D``?TU@&xXmQ*Tv$P)RlHKNF_>urW&W z2?C^^!hJ(O&X|8jOV}r5X!Q}LK1YJ=0Fo8@5hM4SYBy5U-l5iMoQQP-*Au>=BkmKf zM1IEQ@Xx6A{DiZ1lPIy7Mxpr>YFtN=r8SH?pHVu08cusIlid%3>e5J9ZM*{KZI5VR zFM#9r>nODyp*l{KS`2wQhYJU2uSg~^h=Kf~U=r3099W&(X1F1P7gyz#e{7Lk93f(` zvbf;z_vO%8LDaam0@{mDLt|+Q4A-7vL4QLU^);4c!+Fy)cbEvfK}{iydIFF1|Z6u-<3j?FU{w z_8(O5cf8%2*$3UWKF}kpf8?jrFyC|rMjK9n+x5sv^dedR zQzWdpFj$|0!y8XQ=lhf3wwXI2R>?%v?5BK$sdv!p39#N?2162N(@nW>5xopI(KhNl z!PvJl5cYd>o3B>A;N5EG?^uW4P0mesX^ODjQ`F@kb{;l6t6;vN0@mbayhUHZW7{jF zDSSb-%QQ}NHwWB1jKsbD2ormXB*g*5%l0Equ^UzPV`%W6MxFlN|-Sx;`}$6GM};UbCbC8TMM zvsGNal8+!eKMZ2?U7))rj%w1R#>%)LUa#hrUsZ7z>oPa_p{hrFX)c_1U4tG`sp^tw z99&%t`;E5{B-#t}bq&329QF{IuFr<;o-@#29|I@xY9^w=N>^Fz)pAQdG}i=?pyt4ET^6ji zR4{Qh`za4cx0K<;&N?FDWE|WON1q@1-by<2>h1PtTX|ym-#A${I`uCXv+o&Oi>2MP z-%|t+$xCn)y?|poO6fZ;fz9Si@DRHX@7*M#Y9nY4`2}Y!2av8jiZ}%>OQ0Ju(yx&y z*N1GaQMS_Ra?l5~M}K4?f%b&YXbR`{6PQBviND~i#YYsGOyHu|M-*E0quiknO+gdz zmT953Qb2=l1~gVA!gljj8t{{8;6IP-gCoc}{04SgFXPz8dX|Nvu`)K%Nv?($SLKyo zXE7AX7tvpxS75mIG#s~e;_wfpFkD+i4Z9saJKy5yh8D76#V}f13EgE}icA%Ze>j8v zt21D=qlC@)ANV02$9Ggwr)-AR_97hGkcI;r5@GTaS^OUpm{3}7D}d?dEVxQufF+5s zt>_t;Z_b0owp(gPexdg#`AHifnd@1ICGe&H1Gq?m<}UFX%I=WLZC!rlflyo-=jmFUA{|Rjo6S$fD8SU|( z(Gu|)&0)Xbf;W-t@vkU3LXSs(#s&AUIDPN~&O3fWD+zXx%1s)m^I`ZyHV%JZi4&V| zLw7|stVvL7oIau0b`b7jH|h1Pwg^SuT~>MJH&Rp=Cy4k?Z(M`3~z)2K$)UrHRN6AX)t&M}xk7;n&T?^w4r=Ynygv2!q zUecFgur3kiTe7f!eH8o^T41&{okTYd2i7N$Ko`POrU3!+?Qj++TH3~mb2n<1&eJ6MLWfDnID2O?X?8blYllXmSQmDF1`|t6uNjm~gZq!)Dj1 zI~MePSZ*#LN^!V@ zoMA+2u_X^4(nOgXGf5b0;iuS4RGI^4i5eKJkH-lyqSPHZ@Y&k{lT8`07cIewJykfV zc7su^?apEx-jqcIb()c}&CYVTN;JV$tOfQv>TrDLdANwS&}TP5XDt`MO@WjA+2)Sw zZY7>*{`+caSeL8G#<=Ilcb>-a-6brx>L$?wf7vb~$2{2Ys)ZwcudZU3ad;gKv^$y* zq1=lIsUcL^lEn|6LZ1EzQkBM#sxXWMxjw{6_aaa411>mC5upy@R_a%DBut|%mfNu9 zD=zwcMfC|1R`bs&F#JRU`vrA=M8GDasQ3PWQ-*J8u)YAJP093~o`S)O3fOMBf+IiH z;H2!k$qfBBLHRn9ybu7d{Pv6f%G{una{ZHjqVM3a?K;fY*TQaV3yy8R058c~FxhYh z2iK*+jI8~!?S&+u`Sd&!hCjwrhpnK;M7T+vN3c>m9nZ#bu_8KthU|ScTqLXEuUwC# zJ9FV7bAdW^Cj8_ZVX`@$Xtj*aD`V+e9JzAD>MM5@{&LsgE!z&;9W_K*<#3UzLzwD4 zmLF^UV+I$R=(dzh>*#qk$O{$x8+Bsr^S@LicN~q>ZmzQ1k$2BxOAZXzXTx2h6;9%f z@Q`eQuk1BAN>tJJl@I$p6*RaJ#cr!W@ZKlz6@QK}i9wXwki`%Dj7*}|Or=RA$n>$A zrZ9#a-4S+k!H%fUxSq_#TR-DU6p?GdN1XHeMB+-sYWf*@2S4Jh`4`kUf5171Pq-EL zugEfd!4{oZkhmMJ%Z0DZ6BeQ}`=KgdN2ErC*CTo5cU7FW4T+qTdtcxw`Vcl-8sRS1 z1(!XYj4+PxK8FMAl8GwoVYR)O1Tq&EM5vAuWw0d?^;Nh8N3m+SOPz!9rbH&9CnV0m zVmk?`LL;1{N@2IB2v$4u>3yf*y_e`$>=aIjmcxlUxWB>`mLuyS(+FqD^K|Syf|Rep zQ??l{;!W_A>x8p-13hnqx6Cyd(BERPE&&I=Pk5W=aXECTcanFjnZMN+w+1)(X_r@- z{gi|gyGm(ryNnQ(M|6#EP;G~oTr)ydZX;6jK927pXR$pW`s?H9JGp{rjb}u)*AS&N zh!nL^T=e{idjAhZt;2{E?M4QPY|7pdB*_mU-(Vb9LZ)#e@eA6MCU7nOE1FM!!X^K| zpvr-)ztt4-4}PNh1;s}`q4?-9%8yN=$>(R}m=2QbDIf=Q7H;D0u-ks6&286hUR;$| ze&?YAA_uKiNj)|{U4fhEb)wg59Q+{*MjLWS46ETof@dR^LjqUd0B}Az=+uX@i4AF|2pzljs)0iRjjg z&h?PKM4wv=f29_Ls9q<5y$%-=bPu^Y7LRolyNCe!E_(lCgztL@XNfxcyHa4aC$H;5 z)-#how5ZtZ?j0A&a&i)lNIBS#VC4sN%{$2z+(CqP7Y$N%aFed5L8^_# z!~+ytV7-&RAE^uQl)i#6h1Up?=|PU(6zY9GW$ zXbzepVx7jVl)sR;{){V;KeO!x&stBT(s~L-#*@f7Fo8-U)-DU<%HUFN)A$18uRa$-lTx$Tbn9(VB$SZ%Gw@ttJRcjhtLwAh&e7ikhr(E^xn z&W7>UIJipHAW-QtJY;L&qi}%;H49d|v*9CON4CBKmOIjkL@%@m;m>+}nsCrRzk-mtnW-9Erv|Bxt`!f^IMT zWFNBZ1e+bD_k1-jo$IbgqX5~PY$DBJPhD5B&zpdezA3)nyQp3)xS{W(T2}8Ue!A0Lt^y~uy6Bp| zAYpxp812`H*!L3Any(O|b{C#<%|x*`i1=?IT>S>z_SO)s()U1O9HMp&o-&u|x?Uz{ z(uEYQ5tjJRS^bKm)5uW%fJB*oB+3pTokTW$-w-bQeMEiW09*3f8a0g$I=3l=6Vkt+ z!fqOQhF_3pFom4`pV1oj7Ze(g;(E-#(rd$Q8RpM8caCgi z6A5btcfTw|s*~`^H<10mKpnM=I&dw#h+N%>YLAQO(uG5AyoM~0#xe}ta1&R=8uSU8%PLlQHO71L>r*eMr2lxP{k)m zJw)`X^B(b9eTY#VMxy2b;&flaTka}}NEb4U`U^V?#`TBaPyg;j_Vw+tb*abN)10Nw zcDT@W3{~lXi{vHt|A(qRK$O-~q#F&;HGhjlonE@0w-KaD!m4(gxr0c}E_f@}(?Hlj z-x=pD&e4EbN!PfUg%aXaxXoCm&>sH@S^GwjC`Z><<{P!9DU2iEU<{p!A8|YFXS794 z;a2+3XpR1gOM$=OywhJ$ZTAJGmYlGTB2#A!7d$6Xe0chPliw#^T$NXN<=-lPa!qnR z@(n#fO3g&8NhGkRVY54rMDRQUl^ftBUWz3BTVy%QsFqOYt-;Y-?nrjT`T0vU#VNINuu6vG}8m?wzUdxY~rBVKK#Z}$BjM3viU zJj0p${*12luehG{Gdk$J%RxV*C4i{a{xfP%d_?Ynzal|-5NFLlOkQ;R z%-af(S9s;$6_1rDGG9l4w8IIbY$XY4H4$hVLNy!Mv1pA>oRBz89k`x^wiw}B z&FmaknG)EEXORfrN4owK1S+(^Pw^t+^@&=Qn~9_@z(ejl32+zL+zxokUm)vRPn67A z+XiM~{S`aO`aVXHEp>MNaikC-rBTf@oj{h!AYyf&QhiRs{0uRA50Gm7xFA^PLREA5 z-QVo3X0Da=YWb>G*83?};iP&yBDFecKx=}xLIWbTJBik>Bh$Eti2fBa=^7**c#Zh| z-N-Q;M4a9W_{d*@A6@H{tE^d6FTCET7y30vhTm5(*7$7jK5_H zLhJtQ7@N(A?q zKKCAy44=SeNA|t5L7iUxJ)^&wUAJx&4{8dBkfyL+ZhINIB4lLc>pJ3iyJn(Vvm2@&Q>?(-p>%sxXEOm2tF%eMU#jXBH0V zNce*53IB?gkpGEhzptpWpGJ}C&u!($K5ygo5?tazv$qCEb|%7nM*^Ir3K2?{G;Cip3FUQ0xBg0Xh}5}CcAlt8 zyOmzMf|P@gNeEsbl%B`x+@WLFkYWB92}Grdy04LAI*hpeFOhv{0I_O)$TAv7n(;g2 zS`3j8KSP?~TN2erM6OQ|O=25O!t5k=mc+cGwKVv?*YjKb8-A^#TAzFWP=e9b!Wga2 znsk#}h^0X$PWuMjaQW;WN5Mk5F`c5NRgeH1NEk|Mv+p z4)+k1J}1F_LD#nf*~YJsV)y|5>gN%uOV{|oJ%p&X(sjH|M0*=~hewcaJc_2UDO_}) z!YS2BCaxJuACR~26G~0Kp!MVw?xg*UdpTTa;1_fz{(^I!Q)u@6OHYZ-&%C%Qukgx$ zXYp66F?WkDq{5BE&{(`mN%@zjcjl$S?SjBgeMtJh!jQ>!JxqyfeF0TF!*VszWtwaGSl zie%$kNH*$X0}^+Q@-2H2yZ;^vtOt;5)r&&AVH#B4Aj_u!3=o)e%fz(6yiC|mc ztyoI~&UM7jEIPx_<;ncnv4abYzh9qg7SGG0AAshzhCi?uW$-iz0%_(TL4EQR8GVqHLoH> zy`HG_D(oe55w3QH#Fd0X>l)GL6Qmt@h#=(#66F>mu)B!gPn2eG4e6$L$O1n=010&N zv8P0(kC0+?AE!xBGmLsrU^Rp?r%@Cf`G8`ZPbjgS###Gexec$q6)@c#54&A?u-lWB1G@KUHCLglh5E+9s;6G=psN&D|2LH`C4xa(qkpM>*1(hfdE zmI+-ygXajR!7Ib;ISKAF`v2c^*%FA-d`QImgs$~{oHBcfaE&(Pm_McW--DC%S-Q?Q zk!*0A1|crwatEmfeROSyQ1AW)o$H7}0vkR}wi@BUtqk z(n%n=i7{WLYD8*Zq0Zh#V)=rJNwUFRqOvNlhktyks%fOw(7$H76RgeuJ~e-;v1NM20C@U$Ym8)@&!yK93;P z^YB%yftOq*0u<_zr1cD0hn^QkX|>g)**C@4r#~^fd9hpO+0DKUAI2vCOeQG`5hUQv6&Is4Mj5r-G4ecDlROlM$-$A4X4LJ58b1a|&g4 zUvSQeNbC47$g>zm_K~;9HYZDL{t}soU*nAJ01`>4i>>;QbnrT|4nJVR606mTOrkh0 zmKmbj1YeaZL};}jN%s-`t}6)LcL{!q=iseS2`{BmBFgg1QTk0~;Rff63q89+tAk#6 zRmVI$(U|tqq9*pS-Gzi_HWw3LST&{gSQPu-52*Be<(FX6mK&|zQI%?V|4bo?VW!y~ zoH_msr!0vkEgm39tq$QTtwi>XNYd{jF{SHZ&`HF3i>}diqW%tqX&zq6+j@LSsFKKj2C9-!YFs5jZN^CwjL>}zM5s5AZS;hQ zwTrASQR|_bD71cwY|DEnuzXEoL&wb?lQ`ZbI(vtV!!J?dIEs=JA5i7+7ZTPlR6ioe zWR$3Fg2ZYNnoy^fP^N=u!E@YD&qAz5v_FfNNzYlFWU(J1|&c_j8ZhHnt4QU@PdI;M67@jAB=soTol@2_%>Y&`ufI_)H)O)Qly zT>T3D-#1yDG>qsrL7$!_)B9|H!IjXTaXfC!DEVuDtZSq*d~&3Kaa}aL1-kTj{f5W~F-f%m9kLmWbfSh*+ng`BMWL&TWxm96-M3 z1Sz;DcyNhA*}z3qhb#)|)P}61o)lJ*|2&cF7V1LxN!{+FPW=(h!9UP@htNfQ#{H{b zP!sf?l-nCLN57_HY$4BQ3Z;RwL@JYL4S9nyuN5Ng4I%L&j~P<0Q>3h)A=P0JNw&{$ z&yEzeWhbs$wjtGd5Q(-u^qmGMRG*NW13%xS(E7G@50T_F?QcX5h3NMjheV-EJDJ@O zV*jN3N}>*9$aEc(Vqd27IO0yWka}JxLVZDD`iP_^QXHNO$uj{nnO-~DPRE^;bV0t$ z0@CPx&bgNQ&7(EqHGQ6euE{D&{7K25e~C8DKHYHMj@l!oZ=}yA z61}jEn)9UE&(5JNa9R{_)mbL!byBl?s8S!IHS8k{X+IOeenExf5sFV9q1yI)eeNIk zPALDu3KaZ;QR+P}ty>u`!!or+WQ!`lRU|t+LayrsDoK$gIrJiv-Y@o^qfq`0DaEfT zf({K4B`L3(&~>z3+(%8wTQr{EqmcM5>I42N>4Ca)2e=>i1@|w1Phsv$v}$%~`)$+( zzmgm-tGzP6S!AmW^gNGpBI+z6xJ*)@?2V9aKTe;wfa}(zQtf&X`{xD;$&-mFZ=LC( zM>mSxSBNB^6Nx?{GA6+oVAY2_)jZvVjA)M7L{0b{ zo%13JJ!eoIxQ3eGHRvMW(Yd`LmHG<0n73%YctB)(2z~qq6bCGzJ?bs)+CC+s9ieOb zO3pjqbDVB2Q>gOi-1Pw|*pKLp{24C_e#AiHk0>~~H(Y6BR`RL}6#SZ?*O*V_IL(+! z{TD^OwuHQ+aGGiYcx~M}m$G)cLJv2q_pelG1#eqDCutZ92naJfON{F!YJPp#pQ0z4) z?M*4RBgpX>CuKPyQ)8TSWd)mTI}ELDAGG$pq;l!|l2T2uc}T=MMEeYhZ$b)fljk{2 z1U`p+w|S&GJx8%8h2Zo#1@wEas}XnY`{?&sB-;!jkq9%_;|1=KYUN^8rs@Tev=M3c zBhcE=b}q|A)MKP(pP|xslL&cC+SeMx*3lTbiX!hBQTMgyRwd-`y0VM5m_2mF(Ye!g zYKt+GQvHOs*gaCPTj;*Lht}{nbi|eE?=e;U zlX);v8Cg}J;8%?ln?ZHD-MEQKj#X=!&jPp|sfNh3J^Ced;U-BJ6nYye?B~`hBay=< z>WCog&%Z-c#1UGekI)%?EWV+gM6#`ndLU0VgA7u!Tv<<7jiSVFiHLAmh_cdeQwm=RXC6t& zU+lU{g!mX*B0Kh2V8YFJofSgN;DVIhfE3HJRgXXKa#u8YVdm8(7T1lf+$NV0h@ zeXQxK5jw_W$={ZGt;@04lYzG@^fb~aaFqHB|$*U?*@LPfU z8|@#8{f*iRzZL0w&2$+;ZP2=ezPhLlDZJ<|yp#f0Y2X}Mqu)S(?ErO=Cdnx_h8>|P zY#;UKj?jDk3z5hNv_%uiM7%_G$R_Q(i@I~KNa1nQ{WIhenPxhTN&zj42#`AllI)+z z2rv616niXFC{CgIsryK_A0%~aK&s;q%Kg?!Wlqq(FC-^gva|lLEFgnHlX3+tKr&klag0epy0QNmhin3jUnrG zP2p>#4Es@eb^-Zb6VMS!Hk{i=y?Td8caunS9gnqUw8tFDAVG5kg})b%(G>E%cnx%1 zqR=?{E$Sn`qtJLCO&4BE(|tXW5G%imvok30m?okk0uNZC*Onwtnqc(=_v{T)mFJM0 z+oL#7SsA!NA^JFy9iAb@W=KA}+;dHeX6cS&@}0C+Po>kM zk*-5a)F#RTh@gFVpn``YUZRA~fzP`&`jBo&`)H4QPsF-UukF!|hR=Tjts(Ew5xs*F zQvXGs({xVDXb9diHHMg!ys82PzXz218!f5=R!mHUMZS|1)|+tu(k_L;q*|liqMFoJ z=f%%xzp@K`ycr!ae?dpoPiT!erqK2idT)Fo;yp$cZCB*Ggs#{lv|f0Raw4GKtNWq= zn}T1VKKMInmn!y{MODB$DNdabCAU{`=*~T^Om3w*>Iqn{1ZOUjBh&%-DroMbbAeAju|Cc|}@2=j?_B&3ll=5#}W+X7NZ zS*O!}_v}YWl`hJDxsJ1>u(`PP0!`uU6JSJ{zY&cT=9l@-)Ad+GXY9T#u~HZI22B@t z>3V&U9BSv4w}*dyk?{O*ad_1#?5#qLNotpy2n2T;D-;ZSaz*%zqB$ z>RA-}Orb)(Bn2AIqu#%IB$G&-chz6|5&D?FqAlt(+B9Z#UOPlR&)A3WNP6JG6)y1X zpf%D&q_jaH{vyhFd^B)@NNrYz9B!O^AYpr!>zJ6zTtBH7<;teuT(rvbn39PoE;ywT z`Q>{}BhPhCUQaqRK*wB_^}*5{264x>k5np8J{hE^H`{576srLl6z*rL#*ldGvGmMl z5n&elEQ+^66{%w;b{#3qMC(3DLGVhcm%nY6ylo~OubR%kniPEfxw&YX0t{kH|f?J3_qa~ckG~#bWq=z!4)f%;rhV!qXi++bf3bD&c zxiy~OAVtd_uOp-|hltRIQRFcvrYLMMQ{*>`yAF?0;l(C41KPi=yQA zDd|a7&7e@4`{`It&yhl;cuVrIqteQi?au90Q!-l1#jYeLQlkz={K>V3@Aw}*-<$3>H*D0jhjY!V)mQ9z8#&Rlvy9e08tH5=MRPMMGpbAI{ zr`irtm~Rvnnqb?DZ0BiGuk%Q8d4dv8Qj%`-k{;mpDs}@a@S3LI4dB6wo3xMgysD;U z{Pwnu9?1?*kx0t6A#@#OzD(u=bc_k;FTFwg#T^v-&p>~TZYUSc=#Dp|>+&bGXx@{u zKQQa#54E)#lac~Zpg_TY50$|inpVv_Q>*3!p4|EweOLd22b!PIL+Y(2=m1R@KBDL9 zPo(bNqATtYr2(r%I`2vKy^*{nw=k7@Eh5u(Sb9qHJV+tBE+9`e2lhZwV$+D2b3G@C zEC*yHHplfJz63<(N!CQ*J}*$_wSilwdJy~PCZyA6CtCI+mB_V#4Y7%!a~zFC-UgHh z&Y>Y>19|S_XpZD@;C0lU+d+M}33U-BI@iylTnQY_kX$8qB2)*g(EHz^#*h77 znZzE+iU@2V%>^o672)O?y(~wQ>oO|~D(1N?kcu@Bnev$I91-9!GTcUpC|^hm)s0h~ za;y@M6>+ZO@mMZ~@%U?!^#Bs>dL&)IT?$OX9QxMKq+?7<5lhx0vwbQA&)x!e zNilP~SatA%OqgZ67*Oav30=e%YJykL5VcL@x`X!Ek7x`(94_@&TB{T&Q1DMcZMgYF zZP17Ldi4=1{Xd{9>Sxr29H2VHgx1K9XrV`S@GDdWZAoFLI%o+c{?kOp8$wP+9F{v7 zP@tml-gQ!PpX_rQZ>g77D4rf;MVo3jOkw$|7`5=~3d!_4o2+mOAxAYO4*#WIt3;xM zQUqf+tyqf&$)ED%R+=M|=71EmxW6^UaY*`Ib6t$c^&Lln#~doWwk3Cao3=?OMa_c* zoNvu>8xz%9;6JovXbovznZ@|&&jYrmd6tjK*4 zU78(Khs~l{y^Fin{kR|ZnjNyt`R< zdlO_k%%Iqloxq;px>c795^$^6bt}De4ctEU5Y52{NK^HrR=rL)f=Lv5O`-V$6ZNpZ zRK0#e`HL%1py2-uecGQ-=%Nqm+AhC`F8Tu+LibR4b{n-suEoC7Vh&U7zb-jUcHLs@ zJ~nRQu7C^*w|Taoi%#MZ;QXAz^)1}A?3Hjo{&WZOT;^nufX%eIbD+eVkFzM&g;yOr%5vLPp8FKi>_(Azx=-A;_;ntCWu;plNXpk|O~!8XJ!X-3rk_-;frz5*2iR#sV6pg_Sd6xG4&>h@@piI+S{aeOT4fozW5)2 z#GS%!&lNFUNhT%AD*)uUOd`j5nh3C8icdEzdt@Y)yj>wou+hI)706cPg&9aTuY8Nu>nS5DAFCd;*dG(w# zr`e5YYgNh+fC2>yekEuOTT`_}Zg%Imj#Ajaj0(SHBF28{HRWOx6WnzQ?^A7grGiBn zL5=uhIpQt!qFmYBrNDFMt39F0fE4>-Sr(i<2zVHPC%rf=Q0coRBwHS^Ecshb4aiCd zr+H1Tr*!;bWVso{RqHNo&t~1V>g{2j`cR{>s8vW+fdU1;PSmQ`PxM@QqfU1k94_}> zm$s+dR=r4fG$74xOnO^W9S3D~fZL}Y%TnLmubSpGfP8OKwXPE~rpjw#C0aj}@SY7< zcx07Hl}BH%pX?U@ST?@SRvGEI2C*&Fp6)||`+^J{q}V(k&UH6x`v6HY%ga|Zzzs+eRs|9MaKTx`lZlikqEY5R%}gn7?6;ktN*;b3zPA!(+?J|S$5`SJ5H+=g{nY-g5Mn~Jhr|m z@tjwcc&%s>tRLj%yUz`$+6@igv3<0Y=`dxEx44hEZ(GE$MQh!MT<2L_`nJ)W?rhje zw0^vkV*ji=%WbqST{WU*)0rz4?cZoE<`ptkpg@5F1qyzP_zyN4`RKUL%sc=9002ov JPDHLkV1myZcL)Fg literal 0 HcmV?d00001 diff --git a/apps/web/src/assets/react.svg b/apps/web/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/apps/web/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/assets/vite.svg b/apps/web/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/apps/web/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/apps/web/src/components/ui/button.tsx b/apps/web/src/components/ui/button.tsx new file mode 100644 index 0000000..509aa05 --- /dev/null +++ b/apps/web/src/components/ui/button.tsx @@ -0,0 +1,59 @@ +/* eslint-disable react-refresh/only-export-components */ +import { Button as ButtonPrimitive } from "@base-ui/react/button"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", + outline: + "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", + ghost: + "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", + destructive: + "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", + link: "text-primary underline-offset-4 hover:underline" + }, + size: { + default: + "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", + lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + icon: "size-8", + "icon-xs": + "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", + "icon-sm": + "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg", + "icon-lg": "size-9" + } + }, + defaultVariants: { + variant: "default", + size: "default" + } + } +); + +function Button({ + className, + variant = "default", + size = "default", + ...props +}: ButtonPrimitive.Props & VariantProps) { + return ( + + ); +} + +export { Button, buttonVariants }; diff --git a/apps/web/src/index.css b/apps/web/src/index.css new file mode 100644 index 0000000..3239bed --- /dev/null +++ b/apps/web/src/index.css @@ -0,0 +1,21 @@ +@import "@fontsource-variable/geist"; +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --radius: 0.625rem; + --background: #f6f8f7; + --foreground: #122117; + --primary: #0a7a5a; + --primary-foreground: #ffffff; + --border: #d7e2db; + font-family: "Geist Variable", "Noto Sans SC", sans-serif; +} + +body { + margin: 0; + min-height: 100vh; + background: var(--background); + color: var(--foreground); +} diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts new file mode 100644 index 0000000..a5ef193 --- /dev/null +++ b/apps/web/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx new file mode 100644 index 0000000..10ed13e --- /dev/null +++ b/apps/web/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import "./index.css"; +import App from "./App.tsx"; + +createRoot(document.getElementById("root")!).render( + + + +); diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js new file mode 100644 index 0000000..0ded70e --- /dev/null +++ b/apps/web/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ["./index.html", "./src/**/*.{ts,tsx}"], + theme: { + extend: {} + }, + plugins: [] +}; diff --git a/apps/web/tsconfig.app.json b/apps/web/tsconfig.app.json new file mode 100644 index 0000000..c7f7832 --- /dev/null +++ b/apps/web/tsconfig.app.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2023", + "useDefineForClassFields": true, + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..7036885 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "files": [], + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] +} diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/apps/web/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..396a817 --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,13 @@ +import path from "node:path"; +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src") + } + } +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 799a176..cf8728f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,7 +63,7 @@ importers: version: 7.6.0 "@prisma/client": specifier: ^7.6.0 - version: 7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) + version: 7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -118,7 +118,7 @@ importers: version: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)) prisma: specifier: ^7.6.0 - version: 7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 7.6.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) supertest: specifier: ^7.2.2 version: 7.2.2 @@ -135,11 +135,97 @@ importers: specifier: ^5.9.3 version: 5.9.3 + apps/web: + dependencies: + "@base-ui/react": + specifier: ^1.3.0 + version: 1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + "@fontsource-variable/geist": + specifier: ^5.2.8 + version: 5.2.8 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^1.7.0 + version: 1.7.0(react@19.2.4) + react: + specifier: ^19.2.4 + version: 19.2.4 + react-dom: + specifier: ^19.2.4 + version: 19.2.4(react@19.2.4) + shadcn: + specifier: ^4.1.2 + version: 4.1.2(@types/node@24.12.2)(typescript@5.9.3) + tailwind-merge: + specifier: ^3.5.0 + version: 3.5.0 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 + devDependencies: + "@eslint/js": + specifier: ^9.39.4 + version: 9.39.4 + "@types/node": + specifier: ^24.12.0 + version: 24.12.2 + "@types/react": + specifier: ^19.2.14 + version: 19.2.14 + "@types/react-dom": + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + "@vitejs/plugin-react": + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)) + autoprefixer: + specifier: ^10.4.27 + version: 10.4.27(postcss@8.5.8) + eslint: + specifier: ^9.39.4 + version: 9.39.4(jiti@2.6.1) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-react-refresh: + specifier: ^0.5.2 + version: 0.5.2(eslint@9.39.4(jiti@2.6.1)) + globals: + specifier: ^17.4.0 + version: 17.4.0 + postcss: + specifier: ^8.5.8 + version: 8.5.8 + tailwindcss: + specifier: ^3.4.17 + version: 3.4.17(ts-node@10.9.2(@types/node@24.12.2)(typescript@5.9.3)) + typescript: + specifier: ~5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.57.0 + version: 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + vite: + specifier: ^8.0.1 + version: 8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) + packages/eslint-config: {} packages/tsconfig: {} packages: + "@alloc/quick-lru@5.2.0": + resolution: + { + integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + } + engines: { node: ">=10" } + "@aws-crypto/crc32@5.2.0": resolution: { @@ -461,6 +547,13 @@ packages: } engines: { node: ">=6.9.0" } + "@babel/helper-annotate-as-pure@7.27.3": + resolution: + { + integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== + } + engines: { node: ">=6.9.0" } + "@babel/helper-compilation-targets@7.28.6": resolution: { @@ -468,6 +561,15 @@ packages: } engines: { node: ">=6.9.0" } + "@babel/helper-create-class-features-plugin@7.28.6": + resolution: + { + integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + "@babel/helper-globals@7.28.0": resolution: { @@ -475,6 +577,13 @@ packages: } engines: { node: ">=6.9.0" } + "@babel/helper-member-expression-to-functions@7.28.5": + resolution: + { + integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg== + } + engines: { node: ">=6.9.0" } + "@babel/helper-module-imports@7.28.6": resolution: { @@ -491,6 +600,13 @@ packages: peerDependencies: "@babel/core": ^7.0.0 + "@babel/helper-optimise-call-expression@7.27.1": + resolution: + { + integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== + } + engines: { node: ">=6.9.0" } + "@babel/helper-plugin-utils@7.28.6": resolution: { @@ -498,6 +614,22 @@ packages: } engines: { node: ">=6.9.0" } + "@babel/helper-replace-supers@7.28.6": + resolution: + { + integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/helper-skip-transparent-expression-wrappers@7.27.1": + resolution: + { + integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== + } + engines: { node: ">=6.9.0" } + "@babel/helper-string-parser@7.27.1": resolution: { @@ -676,6 +808,40 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 + "@babel/plugin-transform-modules-commonjs@7.28.6": + resolution: + { + integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-typescript@7.28.6": + resolution: + { + integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/preset-typescript@7.28.5": + resolution: + { + integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/runtime@7.29.2": + resolution: + { + integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g== + } + engines: { node: ">=6.9.0" } + "@babel/template@7.28.6": resolution: { @@ -697,6 +863,33 @@ packages: } engines: { node: ">=6.9.0" } + "@base-ui/react@1.3.0": + resolution: + { + integrity: sha512-FwpKqZbPz14AITp1CVgf4AjhKPe1OeeVKSBMdgD10zbFlj3QSWelmtCMLi2+/PFZZcIm3l87G7rwtCZJwHyXWA== + } + engines: { node: ">=14.0.0" } + peerDependencies: + "@types/react": ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@types/react": + optional: true + + "@base-ui/utils@0.2.6": + resolution: + { + integrity: sha512-yQ+qeuqohwhsNpoYDqqXaLllYAkPCP4vYdDrVo8FQXaAPfHWm1pG/Vm+jmGTA5JFS0BAIjookyapuJFY8F9PIw== + } + peerDependencies: + "@types/react": ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@types/react": + optional: true + "@bcoe/v8-coverage@0.2.3": resolution: { @@ -728,6 +921,22 @@ packages: } engines: { node: ">=12" } + "@dotenvx/dotenvx@1.59.1": + resolution: + { + integrity: sha512-Qg+meC+XFxliuVSDlEPkKnaUjdaJKK6FNx/Wwl2UxhQR8pyPIuLhMavsF7ePdB9qFZUWV1jEK3ckbJir/WmF4w== + } + hasBin: true + + "@ecies/ciphers@0.2.6": + resolution: + { + integrity: sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g== + } + engines: { bun: ">=1", deno: ">=2.7.10", node: ">=16" } + peerDependencies: + "@noble/ciphers": ^1.0.0 + "@electric-sql/pglite-socket@0.1.1": resolution: { @@ -785,6 +994,13 @@ packages: } engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + "@eslint/config-array@0.21.2": + resolution: + { + integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + "@eslint/config-array@0.23.4": resolution: { @@ -792,6 +1008,13 @@ packages: } engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + "@eslint/config-helpers@0.4.2": + resolution: + { + integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + "@eslint/config-helpers@0.5.4": resolution: { @@ -799,6 +1022,13 @@ packages: } engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + "@eslint/core@0.17.0": + resolution: + { + integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + "@eslint/core@1.2.0": resolution: { @@ -806,6 +1036,13 @@ packages: } engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + "@eslint/eslintrc@3.3.5": + resolution: + { + integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + "@eslint/js@10.0.1": resolution: { @@ -818,6 +1055,20 @@ packages: eslint: optional: true + "@eslint/js@9.39.4": + resolution: + { + integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/object-schema@2.1.7": + resolution: + { + integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + "@eslint/object-schema@3.0.4": resolution: { @@ -825,6 +1076,13 @@ packages: } engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + "@eslint/plugin-kit@0.4.1": + resolution: + { + integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + "@eslint/plugin-kit@0.7.0": resolution: { @@ -832,6 +1090,39 @@ packages: } engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + "@floating-ui/core@1.7.5": + resolution: + { + integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ== + } + + "@floating-ui/dom@1.7.6": + resolution: + { + integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ== + } + + "@floating-ui/react-dom@2.1.8": + resolution: + { + integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A== + } + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + + "@floating-ui/utils@0.2.11": + resolution: + { + integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg== + } + + "@fontsource-variable/geist@5.2.8": + resolution: + { + integrity: sha512-cJ6m9e+8MQ5dCYJsLylfZrgBh6KkG4bOLckB35Tr9J/EqdkEM6QllH5PxqP1dhTvFup+HtMRPuz9xOjxXJggxw== + } + "@hono/node-server@1.19.11": resolution: { @@ -869,6 +1160,56 @@ packages: } engines: { node: ">=18.18" } + "@inquirer/ansi@1.0.2": + resolution: + { + integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ== + } + engines: { node: ">=18" } + + "@inquirer/confirm@5.1.21": + resolution: + { + integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ== + } + engines: { node: ">=18" } + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + + "@inquirer/core@10.3.2": + resolution: + { + integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A== + } + engines: { node: ">=18" } + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + + "@inquirer/figures@1.0.15": + resolution: + { + integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g== + } + engines: { node: ">=18" } + + "@inquirer/type@3.0.10": + resolution: + { + integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA== + } + engines: { node: ">=18" } + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + "@isaacs/cliui@8.0.2": resolution: { @@ -1076,12 +1417,41 @@ packages: } engines: { node: ">=8" } + "@modelcontextprotocol/sdk@1.29.0": + resolution: + { + integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ== + } + engines: { node: ">=18" } + peerDependencies: + "@cfworker/json-schema": ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + "@cfworker/json-schema": + optional: true + + "@mswjs/interceptors@0.41.3": + resolution: + { + integrity: sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA== + } + engines: { node: ">=18" } + "@napi-rs/wasm-runtime@0.2.12": resolution: { integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== } + "@napi-rs/wasm-runtime@1.1.2": + resolution: + { + integrity: sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw== + } + peerDependencies: + "@emnapi/core": ^1.7.1 + "@emnapi/runtime": ^1.7.1 + "@nestjs/common@11.1.18": resolution: { @@ -1170,6 +1540,20 @@ packages: "@nestjs/platform-express": optional: true + "@noble/ciphers@1.3.0": + resolution: + { + integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw== + } + engines: { node: ^14.21.3 || >=16 } + + "@noble/curves@1.9.7": + resolution: + { + integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw== + } + engines: { node: ^14.21.3 || >=16 } + "@noble/hashes@1.8.0": resolution: { @@ -1184,6 +1568,27 @@ packages: } engines: { node: ">= 20.19.0" } + "@nodelib/fs.scandir@2.1.5": + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + } + engines: { node: ">= 8" } + + "@nodelib/fs.stat@2.0.5": + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + } + engines: { node: ">= 8" } + + "@nodelib/fs.walk@1.2.8": + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + } + engines: { node: ">= 8" } + "@nuxt/opencollective@0.4.1": resolution: { @@ -1192,6 +1597,24 @@ packages: engines: { node: ^14.18.0 || >=16.10.0, npm: ">=5.10.0" } hasBin: true + "@open-draft/deferred-promise@2.2.0": + resolution: + { + integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA== + } + + "@open-draft/logger@0.3.0": + resolution: + { + integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ== + } + + "@open-draft/until@2.1.0": + resolution: + { + integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg== + } + "@otplib/core@12.0.1": resolution: { @@ -1255,6 +1678,12 @@ packages: integrity: sha512-x1ozBa5bPbdZCrrTL/HK21qchiK7jYElTu+0ft22abeEhiLYgH1+SIULvOcVk3CK8YwF4kdcidvkq4ciejucJA== } + "@oxc-project/types@0.122.0": + resolution: + { + integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA== + } + "@paralleldrive/cuid2@2.3.1": resolution: { @@ -1484,18 +1913,177 @@ packages: "@types/react": optional: true + "@rolldown/binding-android-arm64@1.0.0-rc.12": + resolution: + { + integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [android] + + "@rolldown/binding-darwin-arm64@1.0.0-rc.12": + resolution: + { + integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [darwin] + + "@rolldown/binding-darwin-x64@1.0.0-rc.12": + resolution: + { + integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [darwin] + + "@rolldown/binding-freebsd-x64@1.0.0-rc.12": + resolution: + { + integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [freebsd] + + "@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12": + resolution: + { + integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm] + os: [linux] + + "@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12": + resolution: + { + integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [linux] + + "@rolldown/binding-linux-arm64-musl@1.0.0-rc.12": + resolution: + { + integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [linux] + + "@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12": + resolution: + { + integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [ppc64] + os: [linux] + + "@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12": + resolution: + { + integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [s390x] + os: [linux] + + "@rolldown/binding-linux-x64-gnu@1.0.0-rc.12": + resolution: + { + integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [linux] + + "@rolldown/binding-linux-x64-musl@1.0.0-rc.12": + resolution: + { + integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [linux] + + "@rolldown/binding-openharmony-arm64@1.0.0-rc.12": + resolution: + { + integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [openharmony] + + "@rolldown/binding-wasm32-wasi@1.0.0-rc.12": + resolution: + { + integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg== + } + engines: { node: ">=14.0.0" } + cpu: [wasm32] + + "@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12": + resolution: + { + integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [win32] + + "@rolldown/binding-win32-x64-msvc@1.0.0-rc.12": + resolution: + { + integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw== + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [win32] + + "@rolldown/pluginutils@1.0.0-rc.12": + resolution: + { + integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw== + } + + "@rolldown/pluginutils@1.0.0-rc.7": + resolution: + { + integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA== + } + "@scure/base@2.0.0": resolution: { integrity: sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w== } + "@sec-ant/readable-stream@0.4.1": + resolution: + { + integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg== + } + "@sinclair/typebox@0.34.49": resolution: { integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A== } + "@sindresorhus/merge-streams@4.0.0": + resolution: + { + integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ== + } + engines: { node: ">=18" } + "@sinonjs/commons@3.0.1": resolution: { @@ -1898,6 +2486,12 @@ packages: integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== } + "@ts-morph/common@0.27.0": + resolution: + { + integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ== + } + "@tsconfig/node10@1.0.12": resolution: { @@ -2096,6 +2690,12 @@ packages: integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== } + "@types/node@24.12.2": + resolution: + { + integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g== + } + "@types/node@25.5.2": resolution: { @@ -2144,6 +2744,14 @@ packages: integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== } + "@types/react-dom@19.2.3": + resolution: + { + integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== + } + peerDependencies: + "@types/react": ^19.2.0 + "@types/react@19.2.14": resolution: { @@ -2168,6 +2776,12 @@ packages: integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== } + "@types/statuses@2.0.6": + resolution: + { + integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA== + } + "@types/strip-bom@3.0.0": resolution: { @@ -2192,6 +2806,12 @@ packages: integrity: sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw== } + "@types/validate-npm-package-name@4.0.2": + resolution: + { + integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw== + } + "@types/validator@13.15.10": resolution: { @@ -2210,6 +2830,95 @@ packages: integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== } + "@typescript-eslint/eslint-plugin@8.58.0": + resolution: + { + integrity: sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + "@typescript-eslint/parser": ^8.58.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/parser@8.58.0": + resolution: + { + integrity: sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/project-service@8.58.0": + resolution: + { + integrity: sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/scope-manager@8.58.0": + resolution: + { + integrity: sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/tsconfig-utils@8.58.0": + resolution: + { + integrity: sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/type-utils@8.58.0": + resolution: + { + integrity: sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/types@8.58.0": + resolution: + { + integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/typescript-estree@8.58.0": + resolution: + { + integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/utils@8.58.0": + resolution: + { + integrity: sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/visitor-keys@8.58.0": + resolution: + { + integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + "@ungap/structured-clone@1.3.0": resolution: { @@ -2368,6 +3077,22 @@ packages: cpu: [x64] os: [win32] + "@vitejs/plugin-react@6.0.1": + resolution: + { + integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ== + } + engines: { node: ^20.19.0 || >=22.12.0 } + peerDependencies: + "@rolldown/plugin-babel": ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + "@rolldown/plugin-babel": + optional: true + babel-plugin-react-compiler: + optional: true + accepts@2.0.0: resolution: { @@ -2398,6 +3123,24 @@ packages: engines: { node: ">=0.4.0" } hasBin: true + agent-base@7.1.4: + resolution: + { + integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== + } + engines: { node: ">= 14" } + + ajv-formats@3.0.1: + resolution: + { + integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + } + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.14.0: resolution: { @@ -2459,6 +3202,12 @@ packages: } engines: { node: ">=12" } + any-promise@1.3.0: + resolution: + { + integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + } + anymatch@3.1.3: resolution: { @@ -2478,24 +3227,53 @@ packages: integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== } + arg@5.0.2: + resolution: + { + integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + } + argparse@1.0.10: resolution: { integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== } + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + } + asap@2.0.6: resolution: { integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== } + ast-types@0.16.1: + resolution: + { + integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg== + } + engines: { node: ">=4" } + asynckit@0.4.0: resolution: { integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== } + autoprefixer@10.4.27: + resolution: + { + integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA== + } + engines: { node: ^10 || ^12 || >=14 } + hasBin: true + peerDependencies: + postcss: ^8.1.0 + aws-ssl-profiles@1.1.2: resolution: { @@ -2657,6 +3435,13 @@ packages: integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== } + bundle-name@4.1.0: + resolution: + { + integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== + } + engines: { node: ">=18" } + busboy@1.6.0: resolution: { @@ -2703,6 +3488,13 @@ packages: } engines: { node: ">=6" } + camelcase-css@2.0.1: + resolution: + { + integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + } + engines: { node: ">= 6" } + camelcase@5.3.1: resolution: { @@ -2730,6 +3522,13 @@ packages: } engines: { node: ">=10" } + chalk@5.6.2: + resolution: + { + integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== + } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + char-regex@1.0.2: resolution: { @@ -2795,6 +3594,12 @@ packages: integrity: sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw== } + class-variance-authority@0.7.1: + resolution: + { + integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg== + } + cli-cursor@5.0.0: resolution: { @@ -2802,6 +3607,13 @@ packages: } engines: { node: ">=18" } + cli-spinners@2.9.2: + resolution: + { + integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + } + engines: { node: ">=6" } + cli-truncate@5.2.0: resolution: { @@ -2809,6 +3621,13 @@ packages: } engines: { node: ">=20" } + cli-width@4.1.0: + resolution: + { + integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + } + engines: { node: ">= 12" } + cliui@8.0.1: resolution: { @@ -2816,6 +3635,13 @@ packages: } engines: { node: ">=12" } + clsx@2.1.1: + resolution: + { + integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + } + engines: { node: ">=6" } + co@4.6.0: resolution: { @@ -2823,6 +3649,12 @@ packages: } engines: { iojs: ">= 1.0.0", node: ">= 0.12.0" } + code-block-writer@13.0.3: + resolution: + { + integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg== + } + collect-v8-coverage@1.0.3: resolution: { @@ -2855,6 +3687,13 @@ packages: } engines: { node: ">= 0.8" } + commander@11.1.0: + resolution: + { + integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== + } + engines: { node: ">=16" } + commander@14.0.3: resolution: { @@ -2862,6 +3701,13 @@ packages: } engines: { node: ">=20" } + commander@4.1.1: + resolution: + { + integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + } + engines: { node: ">= 6" } + component-emitter@1.3.1: resolution: { @@ -2928,6 +3774,13 @@ packages: } engines: { node: ">= 0.6" } + cookie@1.1.1: + resolution: + { + integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== + } + engines: { node: ">=18" } + cookiejar@2.1.4: resolution: { @@ -2941,6 +3794,18 @@ packages: } engines: { node: ">= 0.10" } + cosmiconfig@9.0.1: + resolution: + { + integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ== + } + engines: { node: ">=14" } + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + create-require@1.1.1: resolution: { @@ -2954,12 +3819,27 @@ packages: } engines: { node: ">= 8" } + cssesc@3.0.0: + resolution: + { + integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + } + engines: { node: ">=4" } + hasBin: true + csstype@3.2.3: resolution: { integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== } + data-uri-to-buffer@4.0.1: + resolution: + { + integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + } + engines: { node: ">= 12" } + debug@4.4.3: resolution: { @@ -3003,6 +3883,27 @@ packages: } engines: { node: ">=0.10.0" } + default-browser-id@5.0.1: + resolution: + { + integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q== + } + engines: { node: ">=18" } + + default-browser@5.5.0: + resolution: + { + integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw== + } + engines: { node: ">=18" } + + define-lazy-prop@3.0.0: + resolution: + { + integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== + } + engines: { node: ">=12" } + defu@6.1.6: resolution: { @@ -3036,6 +3937,13 @@ packages: integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA== } + detect-libc@2.1.2: + resolution: + { + integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + } + engines: { node: ">=8" } + detect-newline@3.1.0: resolution: { @@ -3049,6 +3957,12 @@ packages: integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== } + didyoumean@1.2.2: + resolution: + { + integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + } + diff@4.0.4: resolution: { @@ -3056,6 +3970,19 @@ packages: } engines: { node: ">=0.3.1" } + diff@8.0.4: + resolution: + { + integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw== + } + engines: { node: ">=0.3.1" } + + dlv@1.1.3: + resolution: + { + integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + } + dotenv-expand@12.0.3: resolution: { @@ -3102,6 +4029,13 @@ packages: integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== } + eciesjs@0.4.18: + resolution: + { + integrity: sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ== + } + engines: { bun: ">=1", deno: ">=2", node: ">=16" } + ee-first@1.1.1: resolution: { @@ -3159,6 +4093,13 @@ packages: } engines: { node: ">= 0.8" } + env-paths@2.2.1: + resolution: + { + integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + } + engines: { node: ">=6" } + env-paths@3.0.0: resolution: { @@ -3234,6 +4175,30 @@ packages: } engines: { node: ">=10" } + eslint-plugin-react-hooks@7.0.1: + resolution: + { + integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA== + } + engines: { node: ">=18" } + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.5.2: + resolution: + { + integrity: sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA== + } + peerDependencies: + eslint: ^9 || ^10 + + eslint-scope@8.4.0: + resolution: + { + integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + eslint-scope@9.1.2: resolution: { @@ -3248,6 +4213,13 @@ packages: } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + eslint-visitor-keys@4.2.1: + resolution: + { + integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + eslint-visitor-keys@5.0.1: resolution: { @@ -3268,6 +4240,26 @@ packages: jiti: optional: true + eslint@9.39.4: + resolution: + { + integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + hasBin: true + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: + { + integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + espree@11.2.0: resolution: { @@ -3324,6 +4316,20 @@ packages: integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== } + eventsource-parser@3.0.6: + resolution: + { + integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== + } + engines: { node: ">=18.0.0" } + + eventsource@3.0.7: + resolution: + { + integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA== + } + engines: { node: ">=18.0.0" } + execa@5.1.1: resolution: { @@ -3331,6 +4337,13 @@ packages: } engines: { node: ">=10" } + execa@9.6.1: + resolution: + { + integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA== + } + engines: { node: ^18.19.0 || >=20.5.0 } + exit-x@0.2.2: resolution: { @@ -3345,6 +4358,15 @@ packages: } engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + express-rate-limit@8.3.2: + resolution: + { + integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg== + } + engines: { node: ">= 16" } + peerDependencies: + express: ">= 4.11" + express@5.2.1: resolution: { @@ -3371,6 +4393,13 @@ packages: integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== } + fast-glob@3.3.3: + resolution: + { + integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + } + engines: { node: ">=8.6.0" } + fast-json-stable-stringify@2.1.0: resolution: { @@ -3408,12 +4437,44 @@ packages: } hasBin: true + fastq@1.20.1: + resolution: + { + integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== + } + fb-watchman@2.0.2: resolution: { integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== } + fdir@6.5.0: + resolution: + { + integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + } + engines: { node: ">=12.0.0" } + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fetch-blob@3.2.0: + resolution: + { + integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + } + engines: { node: ^12.20 || >= 14.13 } + + figures@6.1.0: + resolution: + { + integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg== + } + engines: { node: ">=18" } + file-entry-cache@8.0.0: resolution: { @@ -3483,6 +4544,13 @@ packages: } engines: { node: ">= 6" } + formdata-polyfill@4.0.10: + resolution: + { + integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + } + engines: { node: ">=12.20.0" } + formidable@3.5.4: resolution: { @@ -3497,6 +4565,12 @@ packages: } engines: { node: ">= 0.6" } + fraction.js@5.3.4: + resolution: + { + integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ== + } + fresh@2.0.0: resolution: { @@ -3504,6 +4578,13 @@ packages: } engines: { node: ">= 0.8" } + fs-extra@11.3.4: + resolution: + { + integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA== + } + engines: { node: ">=14.14" } + fs.realpath@1.0.0: resolution: { @@ -3524,6 +4605,12 @@ packages: integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== } + fuzzysort@3.1.0: + resolution: + { + integrity: sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ== + } + generate-function@2.3.1: resolution: { @@ -3558,6 +4645,13 @@ packages: } engines: { node: ">= 0.4" } + get-own-enumerable-keys@1.0.0: + resolution: + { + integrity: sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA== + } + engines: { node: ">=14.16" } + get-package-type@0.1.0: resolution: { @@ -3585,6 +4679,13 @@ packages: } engines: { node: ">=10" } + get-stream@9.0.1: + resolution: + { + integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA== + } + engines: { node: ">=18" } + giget@2.0.0: resolution: { @@ -3621,6 +4722,13 @@ packages: } 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 + globals@14.0.0: + resolution: + { + integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + } + engines: { node: ">=18" } + globals@17.4.0: resolution: { @@ -3653,6 +4761,13 @@ packages: integrity: sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg== } + graphql@16.13.2: + resolution: + { + integrity: sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig== + } + engines: { node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0 } + handlebars@4.7.9: resolution: { @@ -3689,6 +4804,24 @@ packages: } engines: { node: ">= 0.4" } + headers-polyfill@4.0.3: + resolution: + { + integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ== + } + + hermes-estree@0.25.1: + resolution: + { + integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== + } + + hermes-parser@0.25.1: + resolution: + { + integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== + } + hono@4.12.10: resolution: { @@ -3715,6 +4848,13 @@ packages: integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA== } + https-proxy-agent@7.0.6: + resolution: + { + integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + } + engines: { node: ">= 14" } + human-signals@2.1.0: resolution: { @@ -3722,6 +4862,13 @@ packages: } engines: { node: ">=10.17.0" } + human-signals@8.0.1: + resolution: + { + integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ== + } + engines: { node: ">=18.18.0" } + husky@9.1.7: resolution: { @@ -3750,6 +4897,20 @@ packages: } engines: { node: ">= 4" } + ignore@7.0.5: + resolution: + { + integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + } + engines: { node: ">= 4" } + + import-fresh@3.3.1: + resolution: + { + integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + } + engines: { node: ">=6" } + import-local@3.2.0: resolution: { @@ -3778,6 +4939,13 @@ packages: integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== } + ip-address@10.1.0: + resolution: + { + integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q== + } + engines: { node: ">= 12" } + ipaddr.js@1.9.1: resolution: { @@ -3805,6 +4973,14 @@ packages: } engines: { node: ">= 0.4" } + is-docker@3.0.0: + resolution: + { + integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + hasBin: true + is-extglob@2.1.1: resolution: { @@ -3840,6 +5016,34 @@ packages: } engines: { node: ">=0.10.0" } + is-in-ssh@1.0.0: + resolution: + { + integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw== + } + engines: { node: ">=20" } + + is-inside-container@1.0.0: + resolution: + { + integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + } + engines: { node: ">=14.16" } + hasBin: true + + is-interactive@2.0.0: + resolution: + { + integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== + } + engines: { node: ">=12" } + + is-node-process@1.2.0: + resolution: + { + integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw== + } + is-number@7.0.0: resolution: { @@ -3847,6 +5051,20 @@ packages: } engines: { node: ">=0.12.0" } + is-obj@3.0.0: + resolution: + { + integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ== + } + engines: { node: ">=12" } + + is-plain-obj@4.1.0: + resolution: + { + integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + } + engines: { node: ">=12" } + is-promise@4.0.0: resolution: { @@ -3859,6 +5077,13 @@ packages: integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== } + is-regexp@3.1.0: + resolution: + { + integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA== + } + engines: { node: ">=12" } + is-stream@2.0.1: resolution: { @@ -3866,12 +5091,47 @@ packages: } engines: { node: ">=8" } + is-stream@4.0.1: + resolution: + { + integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A== + } + engines: { node: ">=18" } + + is-unicode-supported@1.3.0: + resolution: + { + integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== + } + engines: { node: ">=12" } + + is-unicode-supported@2.1.0: + resolution: + { + integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ== + } + engines: { node: ">=18" } + + is-wsl@3.1.1: + resolution: + { + integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw== + } + engines: { node: ">=16" } + isexe@2.0.0: resolution: { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } + isexe@3.1.5: + resolution: + { + integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w== + } + engines: { node: ">=18" } + istanbul-lib-coverage@3.2.2: resolution: { @@ -4123,6 +5383,13 @@ packages: node-notifier: optional: true + jiti@1.21.7: + resolution: + { + integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== + } + hasBin: true + jiti@2.6.1: resolution: { @@ -4130,6 +5397,12 @@ packages: } hasBin: true + jose@6.2.2: + resolution: + { + integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ== + } + js-tokens@4.0.0: resolution: { @@ -4143,6 +5416,13 @@ packages: } hasBin: true + js-yaml@4.1.1: + resolution: + { + integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== + } + hasBin: true + jsesc@3.1.0: resolution: { @@ -4175,6 +5455,12 @@ packages: integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== } + json-schema-typed@8.0.2: + resolution: + { + integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA== + } + json-stable-stringify-without-jsonify@1.0.1: resolution: { @@ -4189,6 +5475,12 @@ packages: engines: { node: ">=6" } hasBin: true + jsonfile@6.2.0: + resolution: + { + integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + } + jsonwebtoken@9.0.3: resolution: { @@ -4214,6 +5506,20 @@ packages: integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== } + kleur@3.0.3: + resolution: + { + integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + } + engines: { node: ">=6" } + + kleur@4.1.5: + resolution: + { + integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== + } + engines: { node: ">=6" } + leven@3.1.0: resolution: { @@ -4234,6 +5540,119 @@ packages: integrity: sha512-lsmMmGXBxXIK/VMLEj0kL6MtUs1kBGj1nTCzi6zgQoG1DEwqwt2DQyHxcLykceIxAnfE3hya7NuIh6PpC6S3fA== } + lightningcss-android-arm64@1.32.0: + resolution: + { + integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg== + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: + { + integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ== + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: + { + integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w== + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: + { + integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig== + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: + { + integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw== + } + engines: { node: ">= 12.0.0" } + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: + { + integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ== + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: + { + integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg== + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: + { + integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA== + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: + { + integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg== + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: + { + integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw== + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: + { + integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q== + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: + { + integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ== + } + engines: { node: ">= 12.0.0" } + + lilconfig@3.1.3: + resolution: + { + integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + } + engines: { node: ">=14" } + lines-and-columns@1.2.4: resolution: { @@ -4318,6 +5737,12 @@ packages: integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== } + lodash.merge@4.6.2: + resolution: + { + integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + } + lodash.once@4.1.1: resolution: { @@ -4330,6 +5755,13 @@ packages: integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== } + log-symbols@6.0.0: + resolution: + { + integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw== + } + engines: { node: ">=18" } + log-update@6.1.0: resolution: { @@ -4362,6 +5794,14 @@ packages: } engines: { bun: ">=1.0.0", deno: ">=1.30.0", node: ">=8.0.0" } + lucide-react@1.7.0: + resolution: + { + integrity: sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg== + } + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + make-dir@4.0.0: resolution: { @@ -4415,6 +5855,13 @@ packages: integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== } + merge2@1.4.1: + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + } + engines: { node: ">= 8" } + methods@1.1.2: resolution: { @@ -4422,6 +5869,13 @@ packages: } engines: { node: ">= 0.6" } + micromatch@4.0.8: + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + } + engines: { node: ">=8.6" } + mime-db@1.52.0: resolution: { @@ -4519,6 +5973,19 @@ packages: integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== } + msw@2.12.14: + resolution: + { + integrity: sha512-4KXa4nVBIBjbDbd7vfQNuQ25eFxug0aropCQFoI0JdOBuJWamkT1yLVIWReFI8SiTRc+H1hKzaNk+cLk2N9rtQ== + } + engines: { node: ">=18" } + hasBin: true + peerDependencies: + typescript: ">= 4.8.x" + peerDependenciesMeta: + typescript: + optional: true + multer@2.1.1: resolution: { @@ -4526,6 +5993,13 @@ packages: } engines: { node: ">= 10.16.0" } + mute-stream@2.0.0: + resolution: + { + integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA== + } + engines: { node: ^18.17.0 || >=20.5.0 } + mysql2@3.15.3: resolution: { @@ -4533,6 +6007,12 @@ packages: } engines: { node: ">= 8.0" } + mz@2.7.0: + resolution: + { + integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + } + named-placeholders@1.1.6: resolution: { @@ -4540,6 +6020,14 @@ packages: } engines: { node: ">=8.0.0" } + nanoid@3.3.11: + resolution: + { + integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + napi-postinstall@0.3.4: resolution: { @@ -4567,12 +6055,27 @@ packages: integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== } + node-domexception@1.0.0: + resolution: + { + integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + } + engines: { node: ">=10.5.0" } + deprecated: Use your platform's native DOMException instead + node-fetch-native@1.6.7: resolution: { integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q== } + node-fetch@3.3.2: + resolution: + { + integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + node-int64@0.4.0: resolution: { @@ -4599,6 +6102,13 @@ packages: } engines: { node: ">=8" } + npm-run-path@6.0.0: + resolution: + { + integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA== + } + engines: { node: ">=18" } + nypm@0.6.5: resolution: { @@ -4620,6 +6130,13 @@ packages: } engines: { node: ">=0.10.0" } + object-hash@3.0.0: + resolution: + { + integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + } + engines: { node: ">= 6" } + object-inspect@1.13.4: resolution: { @@ -4627,6 +6144,13 @@ packages: } engines: { node: ">= 0.4" } + object-treeify@1.1.33: + resolution: + { + integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A== + } + engines: { node: ">= 10" } + ohash@2.0.11: resolution: { @@ -4660,6 +6184,13 @@ packages: } engines: { node: ">=18" } + open@11.0.0: + resolution: + { + integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw== + } + engines: { node: ">=20" } + optionator@0.9.4: resolution: { @@ -4667,12 +6198,25 @@ packages: } engines: { node: ">= 0.8.0" } + ora@8.2.0: + resolution: + { + integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw== + } + engines: { node: ">=18" } + otplib@13.4.0: resolution: { integrity: sha512-RUcYcRMCgRWhUE/XabRppXpUwCwaWBNHe5iPXhdvP8wwDGpGpsIf/kxX/ec3zFsOaM1Oq8lEhUqDwk6W7DHkwg== } + outvariant@1.4.3: + resolution: + { + integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA== + } + p-limit@2.3.0: resolution: { @@ -4714,6 +6258,13 @@ packages: integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== } + parent-module@1.0.1: + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + } + engines: { node: ">=6" } + parse-json@5.2.0: resolution: { @@ -4721,6 +6272,13 @@ packages: } engines: { node: ">=8" } + parse-ms@4.0.0: + resolution: + { + integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw== + } + engines: { node: ">=18" } + parseurl@1.3.3: resolution: { @@ -4756,6 +6314,12 @@ packages: } engines: { node: ">= 0.4.0" } + path-browserify@1.0.1: + resolution: + { + integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + } + path-exists@4.0.0: resolution: { @@ -4784,6 +6348,13 @@ packages: } engines: { node: ">=8" } + path-key@4.0.0: + resolution: + { + integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + } + engines: { node: ">=12" } + path-parse@1.0.7: resolution: { @@ -4797,6 +6368,12 @@ packages: } engines: { node: ">=16 || 14 >=14.18" } + path-to-regexp@6.3.0: + resolution: + { + integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ== + } + path-to-regexp@8.4.2: resolution: { @@ -4899,6 +6476,13 @@ packages: } engines: { node: ">=12" } + pify@2.3.0: + resolution: + { + integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + } + engines: { node: ">=0.10.0" } + pirates@4.0.7: resolution: { @@ -4906,6 +6490,13 @@ packages: } engines: { node: ">= 6" } + pkce-challenge@5.0.1: + resolution: + { + integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ== + } + engines: { node: ">=16.20.0" } + pkg-dir@4.2.0: resolution: { @@ -4919,6 +6510,75 @@ packages: integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig== } + postcss-import@15.1.0: + resolution: + { + integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== + } + engines: { node: ">=14.0.0" } + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: + { + integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw== + } + engines: { node: ^12 || ^14 || >= 16 } + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@4.0.2: + resolution: + { + integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ== + } + engines: { node: ">= 14" } + peerDependencies: + postcss: ">=8.0.9" + ts-node: ">=9.0.0" + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-nested@6.2.0: + resolution: + { + integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ== + } + engines: { node: ">=12.0" } + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: + { + integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + } + engines: { node: ">=4" } + + postcss-selector-parser@7.1.1: + resolution: + { + integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg== + } + engines: { node: ">=4" } + + postcss-value-parser@4.2.0: + resolution: + { + integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + } + + postcss@8.5.8: + resolution: + { + integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg== + } + engines: { node: ^10 || ^12 || >=14 } + postgres-array@2.0.0: resolution: { @@ -4961,6 +6621,13 @@ packages: } engines: { node: ">=12" } + powershell-utils@0.1.0: + resolution: + { + integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A== + } + engines: { node: ">=20" } + prelude-ls@1.2.1: resolution: { @@ -4983,6 +6650,13 @@ packages: } engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + pretty-ms@9.3.0: + resolution: + { + integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ== + } + engines: { node: ">=18" } + prisma@7.6.0: resolution: { @@ -4999,6 +6673,13 @@ packages: typescript: optional: true + prompts@2.4.2: + resolution: + { + integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + } + engines: { node: ">= 6" } + proper-lockfile@4.1.2: resolution: { @@ -5038,6 +6719,12 @@ packages: } engines: { node: ">=0.6" } + queue-microtask@1.2.3: + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + } + range-parser@1.2.1: resolution: { @@ -5079,6 +6766,12 @@ packages: } engines: { node: ">=0.10.0" } + read-cache@1.0.0: + resolution: + { + integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + } + readable-stream@3.6.2: resolution: { @@ -5100,6 +6793,13 @@ packages: } engines: { node: ">= 14.18.0" } + recast@0.23.11: + resolution: + { + integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA== + } + engines: { node: ">= 4" } + reflect-metadata@0.2.2: resolution: { @@ -5126,6 +6826,12 @@ packages: } engines: { node: ">=0.10.0" } + reselect@5.1.1: + resolution: + { + integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w== + } + resolve-cwd@3.0.0: resolution: { @@ -5133,6 +6839,13 @@ packages: } engines: { node: ">=8" } + resolve-from@4.0.0: + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + } + engines: { node: ">=4" } + resolve-from@5.0.0: resolution: { @@ -5162,6 +6875,19 @@ packages: } engines: { node: ">= 4" } + rettime@0.10.1: + resolution: + { + integrity: sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw== + } + + reusify@1.1.0: + resolution: + { + integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + } + engines: { iojs: ">=1.0.0", node: ">=0.10.0" } + rfdc@1.4.1: resolution: { @@ -5176,6 +6902,14 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + rolldown@1.0.0-rc.12: + resolution: + { + integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A== + } + engines: { node: ^20.19.0 || >=22.12.0 } + hasBin: true + router@2.2.0: resolution: { @@ -5183,6 +6917,19 @@ packages: } engines: { node: ">= 18" } + run-applescript@7.1.0: + resolution: + { + integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q== + } + engines: { node: ">=18" } + + run-parallel@1.2.0: + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + } + rxjs@7.8.2: resolution: { @@ -5248,6 +6995,13 @@ packages: integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== } + shadcn@4.1.2: + resolution: + { + integrity: sha512-qNQcCavkbYsgBj+X09tF2bTcwRd8abR880bsFkDU2kMqceMCLAm5c+cLg7kWDhfh1H9g08knpQ5ZEf6y/co16g== + } + hasBin: true + shebang-command@2.0.0: resolution: { @@ -5330,6 +7084,13 @@ packages: } engines: { node: ">=20" } + source-map-js@1.2.1: + resolution: + { + integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + } + engines: { node: ">=0.10.0" } + source-map-support@0.5.13: resolution: { @@ -5389,6 +7150,13 @@ packages: integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== } + stdin-discarder@0.2.2: + resolution: + { + integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== + } + engines: { node: ">=18" } + streamsearch@1.1.0: resolution: { @@ -5396,6 +7164,12 @@ packages: } engines: { node: ">=10.0.0" } + strict-event-emitter@0.5.1: + resolution: + { + integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== + } + string-argv@0.3.2: resolution: { @@ -5444,6 +7218,13 @@ packages: integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== } + stringify-object@5.0.0: + resolution: + { + integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg== + } + engines: { node: ">=14.16" } + strip-ansi@6.0.1: resolution: { @@ -5479,6 +7260,13 @@ packages: } engines: { node: ">=6" } + strip-final-newline@4.0.0: + resolution: + { + integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw== + } + engines: { node: ">=18" } + strip-json-comments@2.0.1: resolution: { @@ -5506,6 +7294,14 @@ packages: } engines: { node: ">=18" } + sucrase@3.35.1: + resolution: + { + integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw== + } + engines: { node: ">=16 || 14 >=14.17" } + hasBin: true + superagent@10.3.0: resolution: { @@ -5548,6 +7344,33 @@ packages: } engines: { node: ^14.18.0 || >=16.0.0 } + tabbable@6.4.0: + resolution: + { + integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg== + } + + tagged-tag@1.0.0: + resolution: + { + integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng== + } + engines: { node: ">=20" } + + tailwind-merge@3.5.0: + resolution: + { + integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A== + } + + tailwindcss@3.4.17: + resolution: + { + integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og== + } + engines: { node: ">=14.0.0" } + hasBin: true + test-exclude@6.0.0: resolution: { @@ -5555,6 +7378,19 @@ packages: } engines: { node: ">=8" } + thenify-all@1.6.0: + resolution: + { + integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + } + engines: { node: ">=0.8" } + + thenify@3.3.1: + resolution: + { + integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + } + thirty-two@1.0.2: resolution: { @@ -5562,6 +7398,12 @@ packages: } engines: { node: ">=0.2.6" } + tiny-invariant@1.3.3: + resolution: + { + integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + } + tinyexec@1.0.4: resolution: { @@ -5569,6 +7411,26 @@ packages: } engines: { node: ">=18" } + tinyglobby@0.2.15: + resolution: + { + integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + } + engines: { node: ">=12.0.0" } + + tldts-core@7.0.27: + resolution: + { + integrity: sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg== + } + + tldts@7.0.27: + resolution: + { + integrity: sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg== + } + hasBin: true + tmpl@1.0.5: resolution: { @@ -5596,6 +7458,13 @@ packages: } engines: { node: ">=14.16" } + tough-cookie@6.0.1: + resolution: + { + integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw== + } + engines: { node: ">=16" } + tree-kill@1.2.2: resolution: { @@ -5603,6 +7472,21 @@ packages: } hasBin: true + ts-api-utils@2.5.0: + resolution: + { + integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== + } + engines: { node: ">=18.12" } + peerDependencies: + typescript: ">=4.8.4" + + ts-interface-checker@0.1.13: + resolution: + { + integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + } + ts-jest@29.4.9: resolution: { @@ -5633,6 +7517,12 @@ packages: jest-util: optional: true + ts-morph@26.0.0: + resolution: + { + integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug== + } + ts-node-dev@2.0.0: resolution: { @@ -5664,6 +7554,13 @@ packages: "@swc/wasm": optional: true + tsconfig-paths@4.2.0: + resolution: + { + integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + } + engines: { node: ">=6" } + tsconfig@7.0.0: resolution: { @@ -5683,6 +7580,12 @@ packages: } hasBin: true + tw-animate-css@1.4.0: + resolution: + { + integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ== + } + type-check@0.4.0: resolution: { @@ -5711,6 +7614,13 @@ packages: } engines: { node: ">=16" } + type-fest@5.5.0: + resolution: + { + integrity: sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g== + } + engines: { node: ">=20" } + type-is@1.6.18: resolution: { @@ -5731,6 +7641,16 @@ packages: integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== } + typescript-eslint@8.58.0: + resolution: + { + integrity: sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + typescript@5.9.3: resolution: { @@ -5767,12 +7687,32 @@ packages: } engines: { node: ">=18" } + undici-types@7.16.0: + resolution: + { + integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + } + undici-types@7.18.2: resolution: { integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== } + unicorn-magic@0.3.0: + resolution: + { + integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA== + } + engines: { node: ">=18" } + + universalify@2.0.1: + resolution: + { + integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + } + engines: { node: ">= 10.0.0" } + unpipe@1.0.0: resolution: { @@ -5786,6 +7726,12 @@ packages: integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg== } + until-async@3.0.2: + resolution: + { + integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw== + } + update-browserslist-db@1.2.3: resolution: { @@ -5801,6 +7747,14 @@ packages: integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== } + use-sync-external-store@1.6.0: + resolution: + { + integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== + } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: { @@ -5838,6 +7792,13 @@ packages: typescript: optional: true + validate-npm-package-name@7.0.2: + resolution: + { + integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A== + } + engines: { node: ^20.17.0 || >=22.9.0 } + validator@13.15.35: resolution: { @@ -5852,12 +7813,65 @@ packages: } engines: { node: ">= 0.8" } + vite@8.0.3: + resolution: + { + integrity: sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ== + } + engines: { node: ^20.19.0 || >=22.12.0 } + hasBin: true + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.1.0 + esbuild: ^0.27.0 + jiti: ">=1.21.0" + less: ^4.0.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 + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + walker@1.0.8: resolution: { integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== } + web-streams-polyfill@3.3.3: + resolution: + { + integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + } + engines: { node: ">= 8" } + which@2.0.2: resolution: { @@ -5866,6 +7880,14 @@ packages: engines: { node: ">= 8" } hasBin: true + which@4.0.0: + resolution: + { + integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg== + } + engines: { node: ^16.13.0 || >=18.0.0 } + hasBin: true + word-wrap@1.2.5: resolution: { @@ -5879,6 +7901,13 @@ packages: integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== } + wrap-ansi@6.2.0: + resolution: + { + integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + } + engines: { node: ">=8" } + wrap-ansi@7.0.0: resolution: { @@ -5913,6 +7942,13 @@ packages: } engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + wsl-utils@0.3.1: + resolution: + { + integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg== + } + engines: { node: ">=20" } + xtend@4.0.2: resolution: { @@ -5969,13 +8005,58 @@ packages: } engines: { node: ">=10" } + yoctocolors-cjs@2.1.3: + resolution: + { + integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw== + } + engines: { node: ">=18" } + + yoctocolors@2.1.2: + resolution: + { + integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug== + } + engines: { node: ">=18" } + zeptomatch@2.1.0: resolution: { integrity: sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA== } + zod-to-json-schema@3.25.2: + resolution: + { + integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA== + } + peerDependencies: + zod: ^3.25.28 || ^4 + + zod-validation-error@4.0.2: + resolution: + { + integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ== + } + engines: { node: ">=18.0.0" } + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@3.25.76: + resolution: + { + integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== + } + + zod@4.3.6: + resolution: + { + integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg== + } + snapshots: + "@alloc/quick-lru@5.2.0": {} + "@aws-crypto/crc32@5.2.0": dependencies: "@aws-crypto/util": 5.2.0 @@ -6474,6 +8555,10 @@ snapshots: "@jridgewell/trace-mapping": 0.3.31 jsesc: 3.1.0 + "@babel/helper-annotate-as-pure@7.27.3": + dependencies: + "@babel/types": 7.29.0 + "@babel/helper-compilation-targets@7.28.6": dependencies: "@babel/compat-data": 7.29.0 @@ -6482,8 +8567,28 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + "@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-annotate-as-pure": 7.27.3 + "@babel/helper-member-expression-to-functions": 7.28.5 + "@babel/helper-optimise-call-expression": 7.27.1 + "@babel/helper-replace-supers": 7.28.6(@babel/core@7.29.0) + "@babel/helper-skip-transparent-expression-wrappers": 7.27.1 + "@babel/traverse": 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + "@babel/helper-globals@7.28.0": {} + "@babel/helper-member-expression-to-functions@7.28.5": + dependencies: + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 + transitivePeerDependencies: + - supports-color + "@babel/helper-module-imports@7.28.6": dependencies: "@babel/traverse": 7.29.0 @@ -6500,8 +8605,28 @@ snapshots: transitivePeerDependencies: - supports-color + "@babel/helper-optimise-call-expression@7.27.1": + dependencies: + "@babel/types": 7.29.0 + "@babel/helper-plugin-utils@7.28.6": {} + "@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-member-expression-to-functions": 7.28.5 + "@babel/helper-optimise-call-expression": 7.27.1 + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-skip-transparent-expression-wrappers@7.27.1": + dependencies: + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 + transitivePeerDependencies: + - supports-color + "@babel/helper-string-parser@7.27.1": {} "@babel/helper-validator-identifier@7.28.5": {} @@ -6602,6 +8727,38 @@ snapshots: "@babel/core": 7.29.0 "@babel/helper-plugin-utils": 7.28.6 + "@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-annotate-as-pure": 7.27.3 + "@babel/helper-create-class-features-plugin": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-skip-transparent-expression-wrappers": 7.27.1 + "@babel/plugin-syntax-typescript": 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + "@babel/preset-typescript@7.28.5(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-validator-option": 7.27.1 + "@babel/plugin-syntax-jsx": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-modules-commonjs": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-typescript": 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + "@babel/runtime@7.29.2": {} + "@babel/template@7.28.6": dependencies: "@babel/code-frame": 7.29.0 @@ -6625,6 +8782,30 @@ snapshots: "@babel/helper-string-parser": 7.27.1 "@babel/helper-validator-identifier": 7.28.5 + "@base-ui/react@1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)": + dependencies: + "@babel/runtime": 7.29.2 + "@base-ui/utils": 0.2.6(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + "@floating-ui/react-dom": 2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + "@floating-ui/utils": 0.2.11 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + tabbable: 6.4.0 + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + "@types/react": 19.2.14 + + "@base-ui/utils@0.2.6(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)": + dependencies: + "@babel/runtime": 7.29.2 + "@floating-ui/utils": 0.2.11 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + reselect: 5.1.1 + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + "@types/react": 19.2.14 + "@bcoe/v8-coverage@0.2.3": {} "@borewit/text-codec@0.2.2": {} @@ -6644,6 +8825,22 @@ snapshots: dependencies: "@jridgewell/trace-mapping": 0.3.9 + "@dotenvx/dotenvx@1.59.1": + dependencies: + commander: 11.1.0 + dotenv: 17.2.3 + eciesjs: 0.4.18 + execa: 5.1.1 + fdir: 6.5.0(picomatch@4.0.4) + ignore: 5.3.2 + object-treeify: 1.1.33 + picomatch: 4.0.4 + which: 4.0.0 + + "@ecies/ciphers@0.2.6(@noble/ciphers@1.3.0)": + dependencies: + "@noble/ciphers": 1.3.0 + "@electric-sql/pglite-socket@0.1.1(@electric-sql/pglite@0.4.1)": dependencies: "@electric-sql/pglite": 0.4.1 @@ -6675,8 +8872,21 @@ snapshots: eslint: 10.2.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 + "@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))": + dependencies: + eslint: 9.39.4(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + "@eslint-community/regexpp@4.12.2": {} + "@eslint/config-array@0.21.2": + dependencies: + "@eslint/object-schema": 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + "@eslint/config-array@0.23.4": dependencies: "@eslint/object-schema": 3.0.4 @@ -6685,25 +8895,75 @@ snapshots: transitivePeerDependencies: - supports-color + "@eslint/config-helpers@0.4.2": + dependencies: + "@eslint/core": 0.17.0 + "@eslint/config-helpers@0.5.4": dependencies: "@eslint/core": 1.2.0 + "@eslint/core@0.17.0": + dependencies: + "@types/json-schema": 7.0.15 + "@eslint/core@1.2.0": dependencies: "@types/json-schema": 7.0.15 + "@eslint/eslintrc@3.3.5": + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + "@eslint/js@10.0.1(eslint@10.2.0(jiti@2.6.1))": optionalDependencies: eslint: 10.2.0(jiti@2.6.1) + "@eslint/js@9.39.4": {} + + "@eslint/object-schema@2.1.7": {} + "@eslint/object-schema@3.0.4": {} + "@eslint/plugin-kit@0.4.1": + dependencies: + "@eslint/core": 0.17.0 + levn: 0.4.1 + "@eslint/plugin-kit@0.7.0": dependencies: "@eslint/core": 1.2.0 levn: 0.4.1 + "@floating-ui/core@1.7.5": + dependencies: + "@floating-ui/utils": 0.2.11 + + "@floating-ui/dom@1.7.6": + dependencies: + "@floating-ui/core": 1.7.5 + "@floating-ui/utils": 0.2.11 + + "@floating-ui/react-dom@2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)": + dependencies: + "@floating-ui/dom": 1.7.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + "@floating-ui/utils@0.2.11": {} + + "@fontsource-variable/geist@5.2.8": {} + "@hono/node-server@1.19.11(hono@4.12.10)": dependencies: hono: 4.12.10 @@ -6719,6 +8979,34 @@ snapshots: "@humanwhocodes/retry@0.4.3": {} + "@inquirer/ansi@1.0.2": {} + + "@inquirer/confirm@5.1.21(@types/node@24.12.2)": + dependencies: + "@inquirer/core": 10.3.2(@types/node@24.12.2) + "@inquirer/type": 3.0.10(@types/node@24.12.2) + optionalDependencies: + "@types/node": 24.12.2 + + "@inquirer/core@10.3.2(@types/node@24.12.2)": + dependencies: + "@inquirer/ansi": 1.0.2 + "@inquirer/figures": 1.0.15 + "@inquirer/type": 3.0.10(@types/node@24.12.2) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + "@types/node": 24.12.2 + + "@inquirer/figures@1.0.15": {} + + "@inquirer/type@3.0.10(@types/node@24.12.2)": + optionalDependencies: + "@types/node": 24.12.2 + "@isaacs/cliui@8.0.2": dependencies: string-width: 5.1.2 @@ -6943,6 +9231,37 @@ snapshots: "@lukeed/csprng@1.1.0": {} + "@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)": + dependencies: + "@hono/node-server": 1.19.11(hono@4.12.10) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.3.2(express@5.2.1) + hono: 4.12.10 + jose: 6.2.2 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color + + "@mswjs/interceptors@0.41.3": + dependencies: + "@open-draft/deferred-promise": 2.2.0 + "@open-draft/logger": 0.3.0 + "@open-draft/until": 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + "@napi-rs/wasm-runtime@0.2.12": dependencies: "@emnapi/core": 1.9.2 @@ -6950,6 +9269,13 @@ snapshots: "@tybys/wasm-util": 0.10.1 optional: true + "@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)": + dependencies: + "@emnapi/core": 1.9.2 + "@emnapi/runtime": 1.9.2 + "@tybys/wasm-util": 0.10.1 + optional: true + "@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)": dependencies: file-type: 21.3.4 @@ -7018,14 +9344,41 @@ snapshots: optionalDependencies: "@nestjs/platform-express": 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18) + "@noble/ciphers@1.3.0": {} + + "@noble/curves@1.9.7": + dependencies: + "@noble/hashes": 1.8.0 + "@noble/hashes@1.8.0": {} "@noble/hashes@2.0.1": {} + "@nodelib/fs.scandir@2.1.5": + dependencies: + "@nodelib/fs.stat": 2.0.5 + run-parallel: 1.2.0 + + "@nodelib/fs.stat@2.0.5": {} + + "@nodelib/fs.walk@1.2.8": + dependencies: + "@nodelib/fs.scandir": 2.1.5 + fastq: 1.20.1 + "@nuxt/opencollective@0.4.1": dependencies: consola: 3.4.2 + "@open-draft/deferred-promise@2.2.0": {} + + "@open-draft/logger@0.3.0": + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + "@open-draft/until@2.1.0": {} + "@otplib/core@12.0.1": {} "@otplib/core@13.4.0": {} @@ -7070,6 +9423,8 @@ snapshots: dependencies: "@otplib/core": 13.4.0 + "@oxc-project/types@0.122.0": {} + "@paralleldrive/cuid2@2.3.1": dependencies: "@noble/hashes": 1.8.0 @@ -7090,11 +9445,11 @@ snapshots: "@prisma/client-runtime-utils@7.6.0": {} - "@prisma/client@7.6.0(prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)": + "@prisma/client@7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)": dependencies: "@prisma/client-runtime-utils": 7.6.0 optionalDependencies: - prisma: 7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + prisma: 7.6.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) typescript: 5.9.3 "@prisma/config@7.6.0": @@ -7168,9 +9523,9 @@ snapshots: env-paths: 3.0.0 proper-lockfile: 4.1.2 - "@prisma/studio-core@0.27.3(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)": + "@prisma/studio-core@0.27.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)": dependencies: - "@radix-ui/react-toggle": 1.1.10(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + "@radix-ui/react-toggle": 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) "@types/react": 19.2.14 chart.js: 4.5.1 react: 19.2.4 @@ -7186,13 +9541,14 @@ snapshots: optionalDependencies: "@types/react": 19.2.14 - "@radix-ui/react-primitive@2.1.3(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)": + "@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)": dependencies: "@radix-ui/react-slot": 1.2.3(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: "@types/react": 19.2.14 + "@types/react-dom": 19.2.3(@types/react@19.2.14) "@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.4)": dependencies: @@ -7201,15 +9557,16 @@ snapshots: optionalDependencies: "@types/react": 19.2.14 - "@radix-ui/react-toggle@1.1.10(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)": + "@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)": dependencies: "@radix-ui/primitive": 1.1.3 - "@radix-ui/react-primitive": 2.1.3(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: "@types/react": 19.2.14 + "@types/react-dom": 19.2.3(@types/react@19.2.14) "@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.4)": dependencies: @@ -7232,10 +9589,68 @@ snapshots: optionalDependencies: "@types/react": 19.2.14 + "@rolldown/binding-android-arm64@1.0.0-rc.12": + optional: true + + "@rolldown/binding-darwin-arm64@1.0.0-rc.12": + optional: true + + "@rolldown/binding-darwin-x64@1.0.0-rc.12": + optional: true + + "@rolldown/binding-freebsd-x64@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-arm64-musl@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-x64-gnu@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-x64-musl@1.0.0-rc.12": + optional: true + + "@rolldown/binding-openharmony-arm64@1.0.0-rc.12": + optional: true + + "@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)": + dependencies: + "@napi-rs/wasm-runtime": 1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + transitivePeerDependencies: + - "@emnapi/core" + - "@emnapi/runtime" + optional: true + + "@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12": + optional: true + + "@rolldown/binding-win32-x64-msvc@1.0.0-rc.12": + optional: true + + "@rolldown/pluginutils@1.0.0-rc.12": {} + + "@rolldown/pluginutils@1.0.0-rc.7": {} + "@scure/base@2.0.0": {} + "@sec-ant/readable-stream@0.4.1": {} + "@sinclair/typebox@0.34.49": {} + "@sindresorhus/merge-streams@4.0.0": {} + "@sinonjs/commons@3.0.1": dependencies: type-detect: 4.0.8 @@ -7587,6 +10002,12 @@ snapshots: "@tokenizer/token@0.3.0": {} + "@ts-morph/common@0.27.0": + dependencies: + fast-glob: 3.3.3 + minimatch: 10.2.5 + path-browserify: 1.0.1 + "@tsconfig/node10@1.0.12": {} "@tsconfig/node12@1.0.11": {} @@ -7695,6 +10116,10 @@ snapshots: "@types/ms@2.1.0": {} + "@types/node@24.12.2": + dependencies: + undici-types: 7.16.0 + "@types/node@25.5.2": dependencies: undici-types: 7.18.2 @@ -7729,6 +10154,10 @@ snapshots: "@types/range-parser@1.2.7": {} + "@types/react-dom@19.2.3(@types/react@19.2.14)": + dependencies: + "@types/react": 19.2.14 + "@types/react@19.2.14": dependencies: csstype: 3.2.3 @@ -7744,6 +10173,8 @@ snapshots: "@types/stack-utils@2.0.3": {} + "@types/statuses@2.0.6": {} + "@types/strip-bom@3.0.0": {} "@types/strip-json-comments@0.0.30": {} @@ -7760,6 +10191,8 @@ snapshots: "@types/methods": 1.1.4 "@types/superagent": 8.1.9 + "@types/validate-npm-package-name@4.0.2": {} + "@types/validator@13.15.10": {} "@types/yargs-parser@21.0.3": {} @@ -7768,6 +10201,97 @@ snapshots: dependencies: "@types/yargs-parser": 21.0.3 + "@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)": + dependencies: + "@eslint-community/regexpp": 4.12.2 + "@typescript-eslint/parser": 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + "@typescript-eslint/scope-manager": 8.58.0 + "@typescript-eslint/type-utils": 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + "@typescript-eslint/utils": 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + "@typescript-eslint/visitor-keys": 8.58.0 + eslint: 9.39.4(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)": + dependencies: + "@typescript-eslint/scope-manager": 8.58.0 + "@typescript-eslint/types": 8.58.0 + "@typescript-eslint/typescript-estree": 8.58.0(typescript@5.9.3) + "@typescript-eslint/visitor-keys": 8.58.0 + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/project-service@8.58.0(typescript@5.9.3)": + dependencies: + "@typescript-eslint/tsconfig-utils": 8.58.0(typescript@5.9.3) + "@typescript-eslint/types": 8.58.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/scope-manager@8.58.0": + dependencies: + "@typescript-eslint/types": 8.58.0 + "@typescript-eslint/visitor-keys": 8.58.0 + + "@typescript-eslint/tsconfig-utils@8.58.0(typescript@5.9.3)": + dependencies: + typescript: 5.9.3 + + "@typescript-eslint/type-utils@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)": + dependencies: + "@typescript-eslint/types": 8.58.0 + "@typescript-eslint/typescript-estree": 8.58.0(typescript@5.9.3) + "@typescript-eslint/utils": 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/types@8.58.0": {} + + "@typescript-eslint/typescript-estree@8.58.0(typescript@5.9.3)": + dependencies: + "@typescript-eslint/project-service": 8.58.0(typescript@5.9.3) + "@typescript-eslint/tsconfig-utils": 8.58.0(typescript@5.9.3) + "@typescript-eslint/types": 8.58.0 + "@typescript-eslint/visitor-keys": 8.58.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/utils@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)": + dependencies: + "@eslint-community/eslint-utils": 4.9.1(eslint@9.39.4(jiti@2.6.1)) + "@typescript-eslint/scope-manager": 8.58.0 + "@typescript-eslint/types": 8.58.0 + "@typescript-eslint/typescript-estree": 8.58.0(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/visitor-keys@8.58.0": + dependencies: + "@typescript-eslint/types": 8.58.0 + eslint-visitor-keys: 5.0.1 + "@ungap/structured-clone@1.3.0": {} "@unrs/resolver-binding-android-arm-eabi@1.11.1": @@ -7829,6 +10353,11 @@ snapshots: "@unrs/resolver-binding-win32-x64-msvc@1.11.1": optional: true + "@vitejs/plugin-react@6.0.1(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3))": + dependencies: + "@rolldown/pluginutils": 1.0.0-rc.7 + vite: 8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -7844,6 +10373,12 @@ snapshots: acorn@8.16.0: {} + agent-base@7.1.4: {} + + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 @@ -7878,6 +10413,8 @@ snapshots: ansi-styles@6.2.3: {} + any-promise@1.3.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -7887,14 +10424,31 @@ snapshots: arg@4.1.3: {} + arg@5.0.2: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 + argparse@2.0.1: {} + asap@2.0.6: {} + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + asynckit@0.4.0: {} + autoprefixer@10.4.27(postcss@8.5.8): + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001785 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + aws-ssl-profiles@1.1.2: {} babel-jest@30.3.0(@babel/core@7.29.0): @@ -8016,6 +10570,10 @@ snapshots: buffer-from@1.1.2: {} + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -8049,6 +10607,8 @@ snapshots: callsites@3.1.0: {} + camelcase-css@2.0.1: {} + camelcase@5.3.1: {} camelcase@6.3.0: {} @@ -8060,6 +10620,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + char-regex@1.0.2: {} chart.js@4.5.1: @@ -8100,23 +10662,35 @@ snapshots: libphonenumber-js: 1.12.41 validator: 13.15.35 + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 + cli-spinners@2.9.2: {} + cli-truncate@5.2.0: dependencies: slice-ansi: 8.0.0 string-width: 8.2.0 + cli-width@4.1.0: {} + cliui@8.0.1: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clsx@2.1.1: {} + co@4.6.0: {} + code-block-writer@13.0.3: {} + collect-v8-coverage@1.0.3: {} color-convert@2.0.1: @@ -8131,8 +10705,12 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@11.1.0: {} + commander@14.0.3: {} + commander@4.1.1: {} + component-emitter@1.3.1: {} concat-map@0.0.1: {} @@ -8158,6 +10736,8 @@ snapshots: cookie@0.7.2: {} + cookie@1.1.1: {} + cookiejar@2.1.4: {} cors@2.8.6: @@ -8165,6 +10745,15 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cosmiconfig@9.0.1(typescript@5.9.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 + create-require@1.1.1: {} cross-spawn@7.0.6: @@ -8173,8 +10762,12 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cssesc@3.0.0: {} + csstype@3.2.3: {} + data-uri-to-buffer@4.0.1: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -8187,6 +10780,15 @@ snapshots: deepmerge@4.3.1: {} + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + defu@6.1.6: {} delayed-stream@1.0.0: {} @@ -8197,6 +10799,8 @@ snapshots: destr@2.0.5: {} + detect-libc@2.1.2: {} + detect-newline@3.1.0: {} dezalgo@1.0.4: @@ -8204,8 +10808,14 @@ snapshots: asap: 2.0.6 wrappy: 1.0.2 + didyoumean@1.2.2: {} + diff@4.0.4: {} + diff@8.0.4: {} + + dlv@1.1.3: {} + dotenv-expand@12.0.3: dependencies: dotenv: 16.6.1 @@ -8230,6 +10840,13 @@ snapshots: dependencies: safe-buffer: 5.2.1 + eciesjs@0.4.18: + dependencies: + "@ecies/ciphers": 0.2.6(@noble/ciphers@1.3.0) + "@noble/ciphers": 1.3.0 + "@noble/curves": 1.9.7 + "@noble/hashes": 1.8.0 + ee-first@1.1.1: {} effect@3.20.0: @@ -8251,6 +10868,8 @@ snapshots: encodeurl@2.0.0: {} + env-paths@2.2.1: {} + env-paths@3.0.0: {} environment@1.1.0: {} @@ -8282,6 +10901,26 @@ snapshots: escape-string-regexp@4.0.0: {} + eslint-plugin-react-hooks@7.0.1(eslint@9.39.4(jiti@2.6.1)): + dependencies: + "@babel/core": 7.29.0 + "@babel/parser": 7.29.2 + eslint: 9.39.4(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.5.2(eslint@9.39.4(jiti@2.6.1)): + dependencies: + eslint: 9.39.4(jiti@2.6.1) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + eslint-scope@9.1.2: dependencies: "@types/esrecurse": 4.3.1 @@ -8291,6 +10930,8 @@ snapshots: eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.1: {} eslint@10.2.0(jiti@2.6.1): @@ -8330,6 +10971,53 @@ snapshots: transitivePeerDependencies: - supports-color + eslint@9.39.4(jiti@2.6.1): + dependencies: + "@eslint-community/eslint-utils": 4.9.1(eslint@9.39.4(jiti@2.6.1)) + "@eslint-community/regexpp": 4.12.2 + "@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.7 + "@humanwhocodes/module-importer": 1.0.1 + "@humanwhocodes/retry": 0.4.3 + "@types/estree": 1.0.8 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + 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.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + espree@11.2.0: dependencies: acorn: 8.16.0 @@ -8354,6 +11042,12 @@ snapshots: eventemitter3@5.0.4: {} + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -8366,6 +11060,21 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + execa@9.6.1: + dependencies: + "@sindresorhus/merge-streams": 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + exit-x@0.2.2: {} expect@30.3.0: @@ -8377,6 +11086,11 @@ snapshots: jest-mock: 30.3.0 jest-util: 30.3.0 + express-rate-limit@8.3.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + express@5.2.1: dependencies: accepts: 2.0.0 @@ -8418,6 +11132,14 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: + dependencies: + "@nodelib/fs.stat": 2.0.5 + "@nodelib/fs.walk": 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -8436,10 +11158,27 @@ snapshots: path-expression-matcher: 1.2.1 strnum: 2.2.2 + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + fb-watchman@2.0.2: dependencies: bser: 2.1.1 + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -8498,6 +11237,10 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + formidable@3.5.4: dependencies: "@paralleldrive/cuid2": 2.3.1 @@ -8506,8 +11249,16 @@ snapshots: forwarded@0.2.0: {} + fraction.js@5.3.4: {} + fresh@2.0.0: {} + fs-extra@11.3.4: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -8515,6 +11266,8 @@ snapshots: function-bind@1.1.2: {} + fuzzysort@3.1.0: {} + generate-function@2.3.1: dependencies: is-property: 1.0.2 @@ -8538,6 +11291,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-own-enumerable-keys@1.0.0: {} + get-package-type@0.1.0: {} get-port-please@3.2.0: {} @@ -8549,6 +11304,11 @@ snapshots: get-stream@6.0.1: {} + get-stream@9.0.1: + dependencies: + "@sec-ant/readable-stream": 0.4.1 + is-stream: 4.0.1 + giget@2.0.0: dependencies: citty: 0.1.6 @@ -8584,6 +11344,8 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + globals@14.0.0: {} + globals@17.4.0: {} gopd@1.2.0: {} @@ -8594,6 +11356,8 @@ snapshots: graphmatch@1.1.1: {} + graphql@16.13.2: {} + handlebars@4.7.9: dependencies: minimist: 1.2.8 @@ -8615,6 +11379,14 @@ snapshots: dependencies: function-bind: 1.1.2 + headers-polyfill@4.0.3: {} + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + hono@4.12.10: {} html-escaper@2.0.2: {} @@ -8629,8 +11401,17 @@ snapshots: http-status-codes@2.3.0: {} + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} + human-signals@8.0.1: {} + husky@9.1.7: {} iconv-lite@0.7.2: @@ -8641,6 +11422,13 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + import-local@3.2.0: dependencies: pkg-dir: 4.2.0 @@ -8655,6 +11443,8 @@ snapshots: inherits@2.0.4: {} + ip-address@10.1.0: {} + ipaddr.js@1.9.1: {} is-arrayish@0.2.1: {} @@ -8667,6 +11457,8 @@ snapshots: dependencies: hasown: 2.0.2 + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -8681,16 +11473,44 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-interactive@2.0.0: {} + + is-node-process@1.2.0: {} + is-number@7.0.0: {} + is-obj@3.0.0: {} + + is-plain-obj@4.1.0: {} + is-promise@4.0.0: {} is-property@1.0.2: {} + is-regexp@3.1.0: {} + is-stream@2.0.1: {} + is-stream@4.0.1: {} + + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + isexe@2.0.0: {} + isexe@3.1.5: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@6.0.3: @@ -9041,8 +11861,12 @@ snapshots: - supports-color - ts-node + jiti@1.21.7: {} + jiti@2.6.1: {} + jose@6.2.2: {} + js-tokens@4.0.0: {} js-yaml@3.14.2: @@ -9050,6 +11874,10 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -9060,10 +11888,18 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + jsonwebtoken@9.0.3: dependencies: jws: 4.0.1 @@ -9092,6 +11928,10 @@ snapshots: dependencies: json-buffer: 3.0.1 + kleur@3.0.3: {} + + kleur@4.1.5: {} + leven@3.1.0: {} levn@0.4.1: @@ -9101,6 +11941,57 @@ snapshots: libphonenumber-js@1.12.41: {} + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lilconfig@3.1.3: {} + lines-and-columns@1.2.4: {} lint-staged@16.4.0: @@ -9145,10 +12036,17 @@ snapshots: lodash.memoize@4.1.2: {} + lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} lodash@4.17.23: {} + log-symbols@6.0.0: + dependencies: + chalk: 5.6.2 + is-unicode-supported: 1.3.0 + log-update@6.1.0: dependencies: ansi-escapes: 7.3.0 @@ -9167,6 +12065,10 @@ snapshots: lru.min@1.1.4: {} + lucide-react@1.7.0(react@19.2.4): + dependencies: + react: 19.2.4 + make-dir@4.0.0: dependencies: semver: 7.7.4 @@ -9187,8 +12089,15 @@ snapshots: merge-stream@2.0.0: {} + merge2@1.4.1: {} + methods@1.1.2: {} + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + mime-db@1.52.0: {} mime-db@1.54.0: {} @@ -9227,6 +12136,31 @@ snapshots: ms@2.1.3: {} + msw@2.12.14(@types/node@24.12.2)(typescript@5.9.3): + dependencies: + "@inquirer/confirm": 5.1.21(@types/node@24.12.2) + "@mswjs/interceptors": 0.41.3 + "@open-draft/deferred-promise": 2.2.0 + "@types/statuses": 2.0.6 + cookie: 1.1.1 + graphql: 16.13.2 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.10.1 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.1 + type-fest: 5.5.0 + until-async: 3.0.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - "@types/node" + multer@2.1.1: dependencies: append-field: 1.0.0 @@ -9234,6 +12168,8 @@ snapshots: concat-stream: 2.0.0 type-is: 1.6.18 + mute-stream@2.0.0: {} + mysql2@3.15.3: dependencies: aws-ssl-profiles: 1.1.2 @@ -9246,10 +12182,18 @@ snapshots: seq-queue: 0.0.5 sqlstring: 2.3.3 + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + named-placeholders@1.1.6: dependencies: lru.min: 1.1.4 + nanoid@3.3.11: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -9258,8 +12202,16 @@ snapshots: neo-async@2.6.2: {} + node-domexception@1.0.0: {} + node-fetch-native@1.6.7: {} + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-int64@0.4.0: {} node-releases@2.0.37: {} @@ -9270,6 +12222,11 @@ snapshots: dependencies: path-key: 3.1.1 + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + nypm@0.6.5: dependencies: citty: 0.2.2 @@ -9280,8 +12237,12 @@ snapshots: object-assign@4.1.1: {} + object-hash@3.0.0: {} + object-inspect@1.13.4: {} + object-treeify@1.1.33: {} + ohash@2.0.11: {} on-finished@2.4.1: @@ -9300,6 +12261,15 @@ snapshots: dependencies: mimic-function: 5.0.1 + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -9309,6 +12279,18 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + ora@8.2.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.2.0 + otplib@13.4.0: dependencies: "@otplib/core": 13.4.0 @@ -9318,6 +12300,8 @@ snapshots: "@otplib/totp": 13.4.0 "@otplib/uri": 13.4.0 + outvariant@1.4.3: {} + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -9338,6 +12322,10 @@ snapshots: package-json-from-dist@1.0.1: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parse-json@5.2.0: dependencies: "@babel/code-frame": 7.29.0 @@ -9345,6 +12333,8 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-ms@4.0.0: {} + parseurl@1.3.3: {} passport-github2@0.1.12: @@ -9367,6 +12357,8 @@ snapshots: pause: 0.0.1 utils-merge: 1.0.1 + path-browserify@1.0.1: {} + path-exists@4.0.0: {} path-expression-matcher@1.2.1: {} @@ -9375,6 +12367,8 @@ snapshots: path-key@3.1.1: {} + path-key@4.0.0: {} + path-parse@1.0.7: {} path-scurry@1.11.1: @@ -9382,6 +12376,8 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.3 + path-to-regexp@6.3.0: {} + path-to-regexp@8.4.2: {} pathe@2.0.3: {} @@ -9431,8 +12427,12 @@ snapshots: picomatch@4.0.4: {} + pify@2.3.0: {} + pirates@4.0.7: {} + pkce-challenge@5.0.1: {} + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -9443,6 +12443,49 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + postcss-import@15.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.8): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.8 + + postcss-load-config@4.0.2(postcss@8.5.8)(ts-node@10.9.2(@types/node@24.12.2)(typescript@5.9.3)): + dependencies: + lilconfig: 3.1.3 + yaml: 2.8.3 + optionalDependencies: + postcss: 8.5.8 + ts-node: 10.9.2(@types/node@24.12.2)(typescript@5.9.3) + + postcss-nested@6.2.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postgres-array@2.0.0: {} postgres-array@3.0.4: {} @@ -9457,6 +12500,8 @@ snapshots: postgres@3.4.7: {} + powershell-utils@0.1.0: {} + prelude-ls@1.2.1: {} prettier@3.8.1: {} @@ -9467,12 +12512,16 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - prisma@7.6.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): dependencies: "@prisma/config": 7.6.0 "@prisma/dev": 0.24.3(typescript@5.9.3) "@prisma/engines": 7.6.0 - "@prisma/studio-core": 0.27.3(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + "@prisma/studio-core": 0.27.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) mysql2: 3.15.3 postgres: 3.4.7 optionalDependencies: @@ -9484,6 +12533,11 @@ snapshots: - react - react-dom + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + proper-lockfile@4.1.2: dependencies: graceful-fs: 4.2.11 @@ -9505,6 +12559,8 @@ snapshots: dependencies: side-channel: 1.1.0 + queue-microtask@1.2.3: {} + range-parser@1.2.1: {} raw-body@3.0.2: @@ -9528,6 +12584,10 @@ snapshots: react@19.2.4: {} + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -9540,6 +12600,14 @@ snapshots: readdirp@4.1.2: {} + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + reflect-metadata@0.2.2: {} remeda@2.33.4: {} @@ -9548,10 +12616,14 @@ snapshots: require-from-string@2.0.2: {} + reselect@5.1.1: {} + resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 + resolve-from@4.0.0: {} + resolve-from@5.0.0: {} resolve@1.22.11: @@ -9567,12 +12639,40 @@ snapshots: retry@0.12.0: {} + rettime@0.10.1: {} + + reusify@1.1.0: {} + rfdc@1.4.1: {} rimraf@2.7.1: dependencies: glob: 7.2.3 + rolldown@1.0.0-rc.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2): + dependencies: + "@oxc-project/types": 0.122.0 + "@rolldown/pluginutils": 1.0.0-rc.12 + optionalDependencies: + "@rolldown/binding-android-arm64": 1.0.0-rc.12 + "@rolldown/binding-darwin-arm64": 1.0.0-rc.12 + "@rolldown/binding-darwin-x64": 1.0.0-rc.12 + "@rolldown/binding-freebsd-x64": 1.0.0-rc.12 + "@rolldown/binding-linux-arm-gnueabihf": 1.0.0-rc.12 + "@rolldown/binding-linux-arm64-gnu": 1.0.0-rc.12 + "@rolldown/binding-linux-arm64-musl": 1.0.0-rc.12 + "@rolldown/binding-linux-ppc64-gnu": 1.0.0-rc.12 + "@rolldown/binding-linux-s390x-gnu": 1.0.0-rc.12 + "@rolldown/binding-linux-x64-gnu": 1.0.0-rc.12 + "@rolldown/binding-linux-x64-musl": 1.0.0-rc.12 + "@rolldown/binding-openharmony-arm64": 1.0.0-rc.12 + "@rolldown/binding-wasm32-wasi": 1.0.0-rc.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + "@rolldown/binding-win32-arm64-msvc": 1.0.0-rc.12 + "@rolldown/binding-win32-x64-msvc": 1.0.0-rc.12 + transitivePeerDependencies: + - "@emnapi/core" + - "@emnapi/runtime" + router@2.2.0: dependencies: debug: 4.4.3 @@ -9583,6 +12683,12 @@ snapshots: transitivePeerDependencies: - supports-color + run-applescript@7.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -9626,6 +12732,49 @@ snapshots: setprototypeof@1.2.0: {} + shadcn@4.1.2(@types/node@24.12.2)(typescript@5.9.3): + dependencies: + "@babel/core": 7.29.0 + "@babel/parser": 7.29.2 + "@babel/plugin-transform-typescript": 7.28.6(@babel/core@7.29.0) + "@babel/preset-typescript": 7.28.5(@babel/core@7.29.0) + "@dotenvx/dotenvx": 1.59.1 + "@modelcontextprotocol/sdk": 1.29.0(zod@3.25.76) + "@types/validate-npm-package-name": 4.0.2 + browserslist: 4.28.2 + commander: 14.0.3 + cosmiconfig: 9.0.1(typescript@5.9.3) + dedent: 1.7.2 + deepmerge: 4.3.1 + diff: 8.0.4 + execa: 9.6.1 + fast-glob: 3.3.3 + fs-extra: 11.3.4 + fuzzysort: 3.1.0 + https-proxy-agent: 7.0.6 + kleur: 4.1.5 + msw: 2.12.14(@types/node@24.12.2)(typescript@5.9.3) + node-fetch: 3.3.2 + open: 11.0.0 + ora: 8.2.0 + postcss: 8.5.8 + postcss-selector-parser: 7.1.1 + prompts: 2.4.2 + recast: 0.23.11 + stringify-object: 5.0.0 + tailwind-merge: 3.5.0 + ts-morph: 26.0.0 + tsconfig-paths: 4.2.0 + validate-npm-package-name: 7.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - "@cfworker/json-schema" + - "@types/node" + - babel-plugin-macros + - supports-color + - typescript + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -9678,6 +12827,8 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + source-map-js@1.2.1: {} + source-map-support@0.5.13: dependencies: buffer-from: 1.1.2 @@ -9704,8 +12855,12 @@ snapshots: std-env@3.10.0: {} + stdin-discarder@0.2.2: {} + streamsearch@1.1.0: {} + strict-event-emitter@0.5.1: {} + string-argv@0.3.2: {} string-length@4.0.2: @@ -9740,6 +12895,12 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-object@5.0.0: + dependencies: + get-own-enumerable-keys: 1.0.0 + is-obj: 3.0.0 + is-regexp: 3.1.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -9754,6 +12915,8 @@ snapshots: strip-final-newline@2.0.0: {} + strip-final-newline@4.0.0: {} + strip-json-comments@2.0.1: {} strip-json-comments@3.1.1: {} @@ -9764,6 +12927,16 @@ snapshots: dependencies: "@tokenizer/token": 0.3.0 + sucrase@3.35.1: + dependencies: + "@jridgewell/gen-mapping": 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + superagent@10.3.0: dependencies: component-emitter: 1.3.1 @@ -9800,16 +12973,70 @@ snapshots: dependencies: "@pkgr/core": 0.2.9 + tabbable@6.4.0: {} + + tagged-tag@1.0.0: {} + + tailwind-merge@3.5.0: {} + + tailwindcss@3.4.17(ts-node@10.9.2(@types/node@24.12.2)(typescript@5.9.3)): + dependencies: + "@alloc/quick-lru": 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.8 + postcss-import: 15.1.0(postcss@8.5.8) + postcss-js: 4.1.0(postcss@8.5.8) + postcss-load-config: 4.0.2(postcss@8.5.8)(ts-node@10.9.2(@types/node@24.12.2)(typescript@5.9.3)) + postcss-nested: 6.2.0(postcss@8.5.8) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.1 + transitivePeerDependencies: + - ts-node + test-exclude@6.0.0: dependencies: "@istanbuljs/schema": 0.1.3 glob: 7.2.3 minimatch: 3.1.5 + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + thirty-two@1.0.2: {} + tiny-invariant@1.3.3: {} + tinyexec@1.0.4: {} + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tldts-core@7.0.27: {} + + tldts@7.0.27: + dependencies: + tldts-core: 7.0.27 + tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -9824,8 +13051,18 @@ snapshots: "@tokenizer/token": 0.3.0 ieee754: 1.2.1 + tough-cookie@6.0.1: + dependencies: + tldts: 7.0.27 + tree-kill@1.2.2: {} + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@30.3.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 @@ -9846,6 +13083,11 @@ snapshots: babel-jest: 30.3.0(@babel/core@7.29.0) jest-util: 30.3.0 + ts-morph@26.0.0: + dependencies: + "@ts-morph/common": 0.27.0 + code-block-writer: 13.0.3 + ts-node-dev@2.0.0(@types/node@25.5.2)(typescript@5.9.3): dependencies: chokidar: 3.6.0 @@ -9864,6 +13106,25 @@ snapshots: - "@swc/wasm" - "@types/node" + ts-node@10.9.2(@types/node@24.12.2)(typescript@5.9.3): + dependencies: + "@cspotcode/source-map-support": 0.8.1 + "@tsconfig/node10": 1.0.12 + "@tsconfig/node12": 1.0.11 + "@tsconfig/node14": 1.0.3 + "@tsconfig/node16": 1.0.4 + "@types/node": 24.12.2 + acorn: 8.16.0 + acorn-walk: 8.3.5 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.4 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optional: true + ts-node@10.9.2(@types/node@25.5.2)(typescript@5.9.3): dependencies: "@cspotcode/source-map-support": 0.8.1 @@ -9882,6 +13143,12 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + tsconfig@7.0.0: dependencies: "@types/strip-bom": 3.0.0 @@ -9900,6 +13167,8 @@ snapshots: "@turbo/windows-64": 2.9.3 "@turbo/windows-arm64": 2.9.3 + tw-animate-css@1.4.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -9910,6 +13179,10 @@ snapshots: type-fest@4.41.0: {} + type-fest@5.5.0: + dependencies: + tagged-tag: 1.0.0 + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -9923,6 +13196,17 @@ snapshots: typedarray@0.0.6: {} + typescript-eslint@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): + dependencies: + "@typescript-eslint/eslint-plugin": 8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + "@typescript-eslint/parser": 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + "@typescript-eslint/typescript-estree": 8.58.0(typescript@5.9.3) + "@typescript-eslint/utils": 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} uglify-js@3.19.3: @@ -9936,8 +13220,14 @@ snapshots: uint8array-extras@1.5.0: {} + undici-types@7.16.0: {} + undici-types@7.18.2: {} + unicorn-magic@0.3.0: {} + + universalify@2.0.1: {} + unpipe@1.0.0: {} unrs-resolver@1.11.1: @@ -9964,6 +13254,8 @@ snapshots: "@unrs/resolver-binding-win32-ia32-msvc": 1.11.1 "@unrs/resolver-binding-win32-x64-msvc": 1.11.1 + until-async@3.0.2: {} + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -9974,6 +13266,10 @@ snapshots: dependencies: punycode: 2.3.1 + use-sync-external-store@1.6.0(react@19.2.4): + dependencies: + react: 19.2.4 + util-deprecate@1.0.2: {} utils-merge@1.0.1: {} @@ -9990,22 +13286,52 @@ snapshots: optionalDependencies: typescript: 5.9.3 + validate-npm-package-name@7.0.2: {} + validator@13.15.35: {} vary@1.1.2: {} + vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.8 + rolldown: 1.0.0-rc.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + tinyglobby: 0.2.15 + optionalDependencies: + "@types/node": 24.12.2 + fsevents: 2.3.3 + jiti: 2.6.1 + yaml: 2.8.3 + transitivePeerDependencies: + - "@emnapi/core" + - "@emnapi/runtime" + walker@1.0.8: dependencies: makeerror: 1.0.12 + web-streams-polyfill@3.3.3: {} + which@2.0.2: dependencies: isexe: 2.0.0 + which@4.0.0: + dependencies: + isexe: 3.1.5 + word-wrap@1.2.5: {} wordwrap@1.0.0: {} + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -10031,6 +13357,11 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + xtend@4.0.2: {} y18n@5.0.8: {} @@ -10055,7 +13386,23 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors-cjs@2.1.3: {} + + yoctocolors@2.1.2: {} + zeptomatch@2.1.0: dependencies: grammex: 3.1.12 graphmatch: 1.1.1 + + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod-validation-error@4.0.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@3.25.76: {} + + zod@4.3.6: {} From fe4f7909e3d6a82677b2fda0428339e91891547d Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 02:35:55 +0800 Subject: [PATCH 28/80] feat(web-auth): implement email code login pages --- apps/web/package.json | 1 + apps/web/src/App.tsx | 32 ++++-- apps/web/src/main.tsx | 5 +- apps/web/src/pages/email-login-page.tsx | 128 ++++++++++++++++++++++++ apps/web/src/pages/todo-shell-page.tsx | 18 ++++ apps/web/src/services/auth-api.ts | 77 ++++++++++++++ pnpm-lock.yaml | 48 +++++++++ 7 files changed, 300 insertions(+), 9 deletions(-) create mode 100644 apps/web/src/pages/email-login-page.tsx create mode 100644 apps/web/src/pages/todo-shell-page.tsx create mode 100644 apps/web/src/services/auth-api.ts diff --git a/apps/web/package.json b/apps/web/package.json index cdebd8a..c83804f 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -17,6 +17,7 @@ "lucide-react": "^1.7.0", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-router-dom": "^7.14.0", "shadcn": "^4.1.2", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.4.0" diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 61e4804..8339add 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,6 +1,13 @@ -import { Button } from "@/components/ui/button"; +import { useState } from "react"; +import { Navigate, Route, Routes, useNavigate } from "react-router-dom"; +import { EmailLoginPage } from "@/pages/email-login-page"; +import { TodoShellPage } from "@/pages/todo-shell-page"; +import type { EmailLoginResult } from "@/services/auth-api"; function App() { + const [session, setSession] = useState(null); + const navigate = useNavigate(); + return (
@@ -9,16 +16,25 @@ function App() {
TodoList
- + {session ? session.user.email : "未登录"}
-
-

Web 壳已就绪

-

- 下一步将接入邮箱验证码登录、OAuth 回调和会话恢复。 -

-
+ + { + setSession(payload); + navigate("/"); + }} + /> + } + /> + } /> + } /> +
); diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 10ed13e..85a16d5 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -1,10 +1,13 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; import "./index.css"; import App from "./App.tsx"; createRoot(document.getElementById("root")!).render( - + + + ); diff --git a/apps/web/src/pages/email-login-page.tsx b/apps/web/src/pages/email-login-page.tsx new file mode 100644 index 0000000..8984cd7 --- /dev/null +++ b/apps/web/src/pages/email-login-page.tsx @@ -0,0 +1,128 @@ +import { useMemo, useState } from "react"; +import type { FormEvent } from "react"; +import { Button } from "@/components/ui/button"; +import { loginWithEmailCode, sendEmailCode, type EmailLoginResult } from "@/services/auth-api"; + +type EmailLoginPageProps = { + onLoginSuccess: (payload: EmailLoginResult) => void; +}; + +export function EmailLoginPage({ onLoginSuccess }: EmailLoginPageProps) { + const [email, setEmail] = useState(""); + const [code, setCode] = useState(""); + const [sendingCode, setSendingCode] = useState(false); + const [loggingIn, setLoggingIn] = useState(false); + const [codeCooldown, setCodeCooldown] = useState(0); + const [message, setMessage] = useState(null); + const [error, setError] = useState(null); + + const canSendCode = useMemo(() => { + return email.trim().length > 0 && !sendingCode && codeCooldown <= 0; + }, [codeCooldown, email, sendingCode]); + + const canLogin = useMemo(() => { + return email.trim().length > 0 && code.trim().length === 6 && !loggingIn; + }, [code, email, loggingIn]); + + async function handleSendCode(event: FormEvent): Promise { + event.preventDefault(); + if (!canSendCode) { + return; + } + + try { + setSendingCode(true); + setError(null); + setMessage(null); + const result = await sendEmailCode(email.trim()); + setMessage(`验证码已发送,有效期 ${result.expiresInSeconds} 秒`); + + let remain = 60; + setCodeCooldown(remain); + const timer = window.setInterval(() => { + remain -= 1; + setCodeCooldown(remain); + if (remain <= 0) { + window.clearInterval(timer); + } + }, 1000); + } catch (err) { + setError(err instanceof Error ? err.message : "发送验证码失败"); + } finally { + setSendingCode(false); + } + } + + async function handleLogin(event: FormEvent): Promise { + event.preventDefault(); + if (!canLogin) { + return; + } + + try { + setLoggingIn(true); + setError(null); + setMessage(null); + const result = await loginWithEmailCode(email.trim(), code.trim()); + onLoginSuccess(result); + } catch (err) { + setError(err instanceof Error ? err.message : "登录失败"); + } finally { + setLoggingIn(false); + } + } + + return ( +
+

邮箱验证码登录

+

输入邮箱后获取验证码,再完成登录。

+ +
+ + setEmail(event.target.value)} + /> + +
+ +
+ + setCode(event.target.value)} + /> + +
+ + {message ?

{message}

: null} + {error ?

{error}

: null} +
+ ); +} diff --git a/apps/web/src/pages/todo-shell-page.tsx b/apps/web/src/pages/todo-shell-page.tsx new file mode 100644 index 0000000..90e9134 --- /dev/null +++ b/apps/web/src/pages/todo-shell-page.tsx @@ -0,0 +1,18 @@ +import type { EmailLoginResult } from "@/services/auth-api"; + +type TodoShellPageProps = { + session: EmailLoginResult | null; +}; + +export function TodoShellPage({ session }: TodoShellPageProps) { + return ( +
+

TodoList 工作台

+

+ {session + ? `当前登录用户:${session.user.email}` + : "当前未建立会话,后续提交会补齐会话恢复和路由守卫。"} +

+
+ ); +} diff --git a/apps/web/src/services/auth-api.ts b/apps/web/src/services/auth-api.ts new file mode 100644 index 0000000..29a014a --- /dev/null +++ b/apps/web/src/services/auth-api.ts @@ -0,0 +1,77 @@ +export type SendEmailCodeResult = { + success: boolean; + expiresInSeconds: number; +}; + +export type EmailLoginResult = { + accessToken: string; + tokenType: "Bearer"; + expiresInSeconds: number; + refreshToken: string; + refreshExpiresInSeconds: number; + user: { + id: string; + email: string; + }; +}; + +const DEFAULT_API_BASE_URL = "http://localhost:3000"; + +function resolveApiBaseUrl(): string { + const envBaseUrl = import.meta.env.VITE_API_BASE_URL as string | undefined; + if (!envBaseUrl) { + return DEFAULT_API_BASE_URL; + } + + return envBaseUrl.replace(/\/+$/, ""); +} + +async function parseErrorMessage(response: Response): Promise { + try { + const body = (await response.json()) as { message?: string | string[] }; + if (Array.isArray(body.message)) { + return body.message.join(";"); + } + if (typeof body.message === "string" && body.message.trim()) { + return body.message; + } + } catch { + return `请求失败(${response.status})`; + } + + return `请求失败(${response.status})`; +} + +export async function sendEmailCode(email: string): Promise { + const response = await fetch(`${resolveApiBaseUrl()}/auth/email/send-code`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ email }) + }); + + if (!response.ok) { + throw new Error(await parseErrorMessage(response)); + } + + const body = (await response.json()) as SendEmailCodeResult; + return body; +} + +export async function loginWithEmailCode(email: string, code: string): Promise { + const response = await fetch(`${resolveApiBaseUrl()}/auth/email/login`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ email, code }) + }); + + if (!response.ok) { + throw new Error(await parseErrorMessage(response)); + } + + const body = (await response.json()) as EmailLoginResult; + return body; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf8728f..dfba0f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -158,6 +158,9 @@ importers: react-dom: specifier: ^19.2.4 version: 19.2.4(react@19.2.4) + react-router-dom: + specifier: ^7.14.0 + version: 7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) shadcn: specifier: ^4.1.2 version: 4.1.2(@types/node@24.12.2)(typescript@5.9.3) @@ -6759,6 +6762,29 @@ packages: integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== } + react-router-dom@7.14.0: + resolution: + { + integrity: sha512-2G3ajSVSZMEtmTjIklRWlNvo8wICEpLihfD/0YMDxbWK2UyP5EGfnoIn9AIQGnF3G/FX0MRbHXdFcD+rL1ZreQ== + } + engines: { node: ">=20.0.0" } + peerDependencies: + react: ">=18" + react-dom: ">=18" + + react-router@7.14.0: + resolution: + { + integrity: sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ== + } + engines: { node: ">=20.0.0" } + peerDependencies: + react: ">=18" + react-dom: ">=18" + peerDependenciesMeta: + react-dom: + optional: true + react@19.2.4: resolution: { @@ -6989,6 +7015,12 @@ packages: } engines: { node: ">= 18" } + set-cookie-parser@2.7.2: + resolution: + { + integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== + } + setprototypeof@1.2.0: resolution: { @@ -12582,6 +12614,20 @@ snapshots: react-is@18.3.1: {} + react-router-dom@7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-router: 7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + react-router@7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + cookie: 1.1.1 + react: 19.2.4 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.4(react@19.2.4) + react@19.2.4: {} read-cache@1.0.0: @@ -12730,6 +12776,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@2.7.2: {} + setprototypeof@1.2.0: {} shadcn@4.1.2(@types/node@24.12.2)(typescript@5.9.3): From 4b47d3bda723625540f3b1831c3a8761ec4a7447 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 02:54:50 +0800 Subject: [PATCH 29/80] feat(web-auth): add oauth callbacks and session bootstrap --- apps/web/src/App.tsx | 40 +++++++++++-- apps/web/src/pages/email-login-page.tsx | 43 +++++++++++--- apps/web/src/pages/oauth-callback-page.tsx | 65 +++++++++++++++++++++ apps/web/src/pages/todo-shell-page.tsx | 8 +-- apps/web/src/services/session-storage.ts | 67 ++++++++++++++++++++++ 5 files changed, 205 insertions(+), 18 deletions(-) create mode 100644 apps/web/src/pages/oauth-callback-page.tsx create mode 100644 apps/web/src/services/session-storage.ts diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 8339add..9765446 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,11 +1,24 @@ -import { useState } from "react"; +import { useState } from "react"; import { Navigate, Route, Routes, useNavigate } from "react-router-dom"; import { EmailLoginPage } from "@/pages/email-login-page"; +import { OAuthCallbackPage } from "@/pages/oauth-callback-page"; import { TodoShellPage } from "@/pages/todo-shell-page"; import type { EmailLoginResult } from "@/services/auth-api"; +import { loadSession, saveSession, type WebSession } from "@/services/session-storage"; + +function toWebSession(payload: EmailLoginResult): WebSession { + return { + accessToken: payload.accessToken, + refreshToken: payload.refreshToken, + user: { + id: payload.user.id, + email: payload.user.email + } + }; +} function App() { - const [session, setSession] = useState(null); + const [session, setSession] = useState(() => loadSession()); const navigate = useNavigate(); return ( @@ -26,14 +39,31 @@ function App() { element={ { - setSession(payload); + const nextSession = toWebSession(payload); + saveSession(nextSession); + setSession(nextSession); navigate("/"); }} /> } /> - } /> - } /> + { + setSession(nextSession); + }} + /> + } + /> + : + } + /> + } /> diff --git a/apps/web/src/pages/email-login-page.tsx b/apps/web/src/pages/email-login-page.tsx index 8984cd7..fe17509 100644 --- a/apps/web/src/pages/email-login-page.tsx +++ b/apps/web/src/pages/email-login-page.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import type { FormEvent } from "react"; import { Button } from "@/components/ui/button"; import { loginWithEmailCode, sendEmailCode, type EmailLoginResult } from "@/services/auth-api"; @@ -7,6 +7,17 @@ type EmailLoginPageProps = { onLoginSuccess: (payload: EmailLoginResult) => void; }; +const DEFAULT_API_BASE_URL = "http://localhost:3000"; + +function resolveApiBaseUrl(): string { + const envBaseUrl = import.meta.env.VITE_API_BASE_URL as string | undefined; + if (!envBaseUrl) { + return DEFAULT_API_BASE_URL; + } + + return envBaseUrl.replace(/\/+$/, ""); +} + export function EmailLoginPage({ onLoginSuccess }: EmailLoginPageProps) { const [email, setEmail] = useState(""); const [code, setCode] = useState(""); @@ -35,7 +46,7 @@ export function EmailLoginPage({ onLoginSuccess }: EmailLoginPageProps) { setError(null); setMessage(null); const result = await sendEmailCode(email.trim()); - setMessage(`验证码已发送,有效期 ${result.expiresInSeconds} 秒`); + setMessage(`验证码已发送,有效期 ${result.expiresInSeconds} 秒。`); let remain = 60; setCodeCooldown(remain); @@ -75,7 +86,9 @@ export function EmailLoginPage({ onLoginSuccess }: EmailLoginPageProps) { return (

邮箱验证码登录

-

输入邮箱后获取验证码,再完成登录。

+

+ 输入邮箱后获取验证码,再完成登录。你也可以直接使用第三方账号登录。 +

diff --git a/apps/web/src/pages/oauth-callback-page.tsx b/apps/web/src/pages/oauth-callback-page.tsx new file mode 100644 index 0000000..f4ae5f3 --- /dev/null +++ b/apps/web/src/pages/oauth-callback-page.tsx @@ -0,0 +1,65 @@ +import { useMemo } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import { Button } from "@/components/ui/button"; +import { saveSession, type WebSession } from "@/services/session-storage"; + +type OAuthCallbackPageProps = { + onBootstrapSession: (session: WebSession) => void; +}; + +export function OAuthCallbackPage({ onBootstrapSession }: OAuthCallbackPageProps) { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + + const parseResult = useMemo(() => { + const accessToken = searchParams.get("accessToken"); + const refreshToken = searchParams.get("refreshToken"); + const userId = searchParams.get("userId"); + const email = searchParams.get("email"); + + if (!accessToken || !refreshToken || !userId || !email) { + return { + ok: false as const, + reason: "回调参数不完整,暂时无法建立会话。" + }; + } + + return { + ok: true as const, + session: { + accessToken, + refreshToken, + user: { + id: userId, + email + } + } + }; + }, [searchParams]); + + function handleContinue(): void { + if (!parseResult.ok) { + navigate("/login/email", { replace: true }); + return; + } + + saveSession(parseResult.session); + onBootstrapSession(parseResult.session); + navigate("/", { replace: true }); + } + + return ( +
+

OAuth 回调处理中

+

+ {parseResult.ok ? "已收到回调参数,点击继续进入工作台。" : parseResult.reason} +

+ +
+ ); +} diff --git a/apps/web/src/pages/todo-shell-page.tsx b/apps/web/src/pages/todo-shell-page.tsx index 90e9134..737789c 100644 --- a/apps/web/src/pages/todo-shell-page.tsx +++ b/apps/web/src/pages/todo-shell-page.tsx @@ -1,7 +1,7 @@ -import type { EmailLoginResult } from "@/services/auth-api"; +import type { WebSession } from "@/services/session-storage"; type TodoShellPageProps = { - session: EmailLoginResult | null; + session: WebSession | null; }; export function TodoShellPage({ session }: TodoShellPageProps) { @@ -9,9 +9,7 @@ export function TodoShellPage({ session }: TodoShellPageProps) {

TodoList 工作台

- {session - ? `当前登录用户:${session.user.email}` - : "当前未建立会话,后续提交会补齐会话恢复和路由守卫。"} + {session ? `当前登录邮箱:${session.user.email}` : "当前未建立登录会话,请先完成登录。"}

); diff --git a/apps/web/src/services/session-storage.ts b/apps/web/src/services/session-storage.ts new file mode 100644 index 0000000..99963ba --- /dev/null +++ b/apps/web/src/services/session-storage.ts @@ -0,0 +1,67 @@ +import type { EmailLoginResult } from "@/services/auth-api"; + +const SESSION_STORAGE_KEY = "todolist.web.session"; + +export type WebSession = { + accessToken: string; + refreshToken: string; + user: { + id: string; + email: string; + }; +}; + +function isValidSession(payload: unknown): payload is WebSession { + if (!payload || typeof payload !== "object") { + return false; + } + + const data = payload as { + accessToken?: unknown; + refreshToken?: unknown; + user?: { + id?: unknown; + email?: unknown; + }; + }; + + return ( + typeof data.accessToken === "string" && + typeof data.refreshToken === "string" && + typeof data.user?.id === "string" && + typeof data.user?.email === "string" + ); +} + +export function loadSession(): WebSession | null { + const raw = window.localStorage.getItem(SESSION_STORAGE_KEY); + if (!raw) { + return null; + } + + try { + const parsed = JSON.parse(raw) as unknown; + if (!isValidSession(parsed)) { + return null; + } + return parsed; + } catch { + return null; + } +} + +export function saveSession(payload: EmailLoginResult | WebSession): void { + const session: WebSession = { + accessToken: payload.accessToken, + refreshToken: payload.refreshToken, + user: { + id: payload.user.id, + email: payload.user.email + } + }; + window.localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(session)); +} + +export function clearSession(): void { + window.localStorage.removeItem(SESSION_STORAGE_KEY); +} From 48b69793ce02ab25f69adbc1f697e85cdc007b77 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 14:28:13 +0800 Subject: [PATCH 30/80] docs(api-env): improve .env.example comments and grouping --- apps/api/.env.example | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/apps/api/.env.example b/apps/api/.env.example index 68af79c..971b829 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -1,27 +1,65 @@ +# ----------------------------------------------------------------------------- +# TodoList API 环境变量示例 +# 用法: +# 1) 复制为 apps/api/.env +# 2) 按实际环境替换值(尤其是密钥、密码、令牌) +# ----------------------------------------------------------------------------- + +# [数据库] PostgreSQL 连接串 +# 格式:postgresql://:@:/?schema=public DATABASE_URL="postgresql://postgres:postgres@localhost:5432/todolist?schema=public" + +# [鉴权] Access Token 签名密钥(生产环境必须使用高强度随机值) AUTH_ACCESS_SECRET="dev-access-secret" +# [鉴权] Access Token 有效期(秒),默认 15 分钟 AUTH_ACCESS_EXPIRES_IN_SECONDS="900" +# [鉴权] Refresh Token 有效期(秒),默认 30 天 AUTH_REFRESH_EXPIRES_IN_SECONDS="2592000" +# [鉴权] 邮箱验证码有效期(秒),默认 5 分钟 AUTH_EMAIL_CODE_TTL_SECONDS="300" +# [2FA] TOTP 签发方名称(会显示在验证器 App 中) AUTH_TOTP_ISSUER="TodoList" + +# [OAuth - GitHub] 第三方登录配置 OAUTH_GITHUB_CLIENT_ID="github-client-id" OAUTH_GITHUB_CLIENT_SECRET="github-client-secret" OAUTH_GITHUB_CALLBACK_URL="http://localhost:3000/auth/oauth/github/callback" + +# [OAuth - QQ] 第三方登录配置 OAUTH_QQ_CLIENT_ID="qq-client-id" OAUTH_QQ_CLIENT_SECRET="qq-client-secret" OAUTH_QQ_CALLBACK_URL="http://localhost:3000/auth/oauth/qq/callback" OAUTH_QQ_AUTH_URL="https://graph.qq.com/oauth2.0/authorize" OAUTH_QQ_TOKEN_URL="https://graph.qq.com/oauth2.0/token" + +# [OAuth - 微信] 第三方登录配置 OAUTH_WECHAT_CLIENT_ID="wechat-client-id" OAUTH_WECHAT_CLIENT_SECRET="wechat-client-secret" OAUTH_WECHAT_CALLBACK_URL="http://localhost:3000/auth/oauth/wechat/callback" OAUTH_WECHAT_AUTH_URL="https://open.weixin.qq.com/connect/qrconnect" OAUTH_WECHAT_TOKEN_URL="https://api.weixin.qq.com/sns/oauth2/access_token" + +# [对象存储] S3/MinIO 配置(附件上传) +# 本地开发可使用 MinIO,生产可切换到云厂商 S3 兼容服务 S3_ENDPOINT="http://127.0.0.1:9000" S3_REGION="us-east-1" S3_BUCKET="todolist" S3_ACCESS_KEY_ID="minioadmin" S3_SECRET_ACCESS_KEY="minioadmin" +# MinIO 常用 true;AWS S3 常用 false S3_FORCE_PATH_STYLE="true" +# 预签名上传 URL 的有效期(秒) S3_PRESIGN_EXPIRES_SECONDS="900" +# 对外访问附件的基础地址(用于拼接公开 URL) S3_PUBLIC_BASE_URL="http://127.0.0.1:9000" + +# [邮件] SMTP 配置(验证码/DDL 提醒邮件) +MAIL_SMTP_HOST="smtp.example.com" +MAIL_SMTP_PORT="465" +# 465 通常为 true(SSL),587 通常为 false(STARTTLS) +MAIL_SMTP_SECURE="true" +MAIL_SMTP_USER="no-reply@example.com" +MAIL_SMTP_PASS="replace-with-smtp-password" +# 发件人显示名称与地址 +MAIL_FROM_NAME="TodoList" +MAIL_FROM_ADDRESS="no-reply@example.com" From aae03b6b0d469adfea8771dea69fae0f02b1d5b5 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 14:28:25 +0800 Subject: [PATCH 31/80] docs(readme): add deployment and usage guide --- README.md | 125 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 108 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 29367df..ded8f13 100644 --- a/README.md +++ b/README.md @@ -71,23 +71,23 @@ > 状态说明:`[x]` 已完成,`[ ]` 进行中/未开始(请随开发进度更新) -| 顺序 | 功能实现项(用户视角) | 你会看到的效果 | 状态 | -|---|---|---|---| -| 1 | 明确产品能力与交互流程 | 确认 TodoList 的核心使用方式与页面路径 | [x] | -| 2 | 实现基础登录(邮箱验证码) | 可以注册/登录并进入主页面 | [ ] | -| 3 | 实现任务基础能力(增删改查) | 可以创建、编辑、删除、完成任务 | [ ] | -| 4 | 实现富文本与媒体内容 | 任务详情可插入图片、视频、链接等内容 | [ ] | -| 5 | 实现本地离线存储(Dexie) | 无网时仍可打开并编辑任务 | [ ] | -| 6 | 实现云端同步与冲突处理 | 恢复网络后自动同步,冲突按规则合并 | [ ] | -| 7 | 实现提醒系统(邮件) | DDL 临近时收到邮件提醒 | [ ] | -| 8 | 实现 AI 问答(用户自带 Key) | 可直接用自己的 AI API Key 获取建议 | [ ] | -| 9 | 实现 Astrbot Provider 接入 | 可复用 Astrbot 内配置的 AI 提供商 | [ ] | -| 10 | 实现公共 AI 通道(可开关) | 管理员开启后,用户可直接使用站点公共 AI | [ ] | -| 11 | 实现 Astrbot Skill 对接 | 可通过 QQ 机器人添加/修改任务与获取建议 | [ ] | -| 12 | 实现完整账号安全(2FA + OAuth) | 支持 2FA、QQ/微信/GitHub 登录 | [ ] | -| 13 | 实现 PWA 安装与离线体验优化 | 支持“添加到桌面”,像本地 App 一样使用 | [ ] | -| 14 | 实现管理后台(配额/日志/系统配置) | 管理员可管理用户配额、站点信息、日志 | [ ] | -| 15 | 上线前安全与性能收尾 | 使用更稳定、更安全,核心链路可观测 | [ ] | +| 顺序 | 功能实现项(用户视角) | 你会看到的效果 | 状态 | +| ---- | ---------------------------------- | --------------------------------------- | ---- | +| 1 | 明确产品能力与交互流程 | 确认 TodoList 的核心使用方式与页面路径 | [x] | +| 2 | 实现基础登录(邮箱验证码) | 可以注册/登录并进入主页面 | [ ] | +| 3 | 实现任务基础能力(增删改查) | 可以创建、编辑、删除、完成任务 | [ ] | +| 4 | 实现富文本与媒体内容 | 任务详情可插入图片、视频、链接等内容 | [ ] | +| 5 | 实现本地离线存储(Dexie) | 无网时仍可打开并编辑任务 | [ ] | +| 6 | 实现云端同步与冲突处理 | 恢复网络后自动同步,冲突按规则合并 | [ ] | +| 7 | 实现提醒系统(邮件) | DDL 临近时收到邮件提醒 | [ ] | +| 8 | 实现 AI 问答(用户自带 Key) | 可直接用自己的 AI API Key 获取建议 | [ ] | +| 9 | 实现 Astrbot Provider 接入 | 可复用 Astrbot 内配置的 AI 提供商 | [ ] | +| 10 | 实现公共 AI 通道(可开关) | 管理员开启后,用户可直接使用站点公共 AI | [ ] | +| 11 | 实现 Astrbot Skill 对接 | 可通过 QQ 机器人添加/修改任务与获取建议 | [ ] | +| 12 | 实现完整账号安全(2FA + OAuth) | 支持 2FA、QQ/微信/GitHub 登录 | [ ] | +| 13 | 实现 PWA 安装与离线体验优化 | 支持“添加到桌面”,像本地 App 一样使用 | [ ] | +| 14 | 实现管理后台(配额/日志/系统配置) | 管理员可管理用户配额、站点信息、日志 | [ ] | +| 15 | 上线前安全与性能收尾 | 使用更稳定、更安全,核心链路可观测 | [ ] | --- @@ -151,6 +151,97 @@ TodoList/ --- +## 部署与使用 + +### 1. 环境要求 + +- Node.js `20.x` +- pnpm `9.15.2` +- PostgreSQL `14+`(本地或远程都可) +- 可选:MinIO / S3(附件上传功能使用) + +### 2. 安装依赖 + +```bash +pnpm install +``` + +### 3. 后端环境变量配置 + +1. 复制环境变量示例文件: + +```bash +cp apps/api/.env.example apps/api/.env +# PowerShell: +# Copy-Item apps/api/.env.example apps/api/.env +``` + +2. 至少修改以下配置: + +- `DATABASE_URL`:你的 PostgreSQL 连接串 +- `AUTH_ACCESS_SECRET`:生产环境请改为高强度随机值 +- `MAIL_SMTP_*`:邮件服务器配置(验证码/提醒邮件) +- `OAUTH_*`:第三方登录配置(未接入可先保留示例值) +- `S3_*`:对象存储配置(未启用附件可后续再配) + +### 4. 初始化数据库 + +```bash +pnpm --filter @todolist/api exec prisma db push +``` + +### 5. 本地开发启动 + +1. 启动后端(默认端口 `3000`): + +```bash +pnpm --filter @todolist/api start:dev +``` + +2. 启动前端(默认端口 `5173`): + +```bash +pnpm --filter web dev +``` + +3. 若前端需连接非默认后端地址,可设置: + +```bash +VITE_API_BASE_URL=http://localhost:3000 +``` + +### 6. 生产构建与运行 + +1. 构建: + +```bash +pnpm run build +``` + +2. 运行 API(需先构建): + +```bash +pnpm --filter @todolist/api start +``` + +3. 发布 Web: + +- `apps/web/dist` 为静态资源产物,建议使用 Nginx/静态托管服务发布。 + +### 7. CI/CD 说明(当前仓库) + +- PR 质量检查:`.github/workflows/pr-quality.yml` +- Web 部署模板:`.github/workflows/deploy-web.yml` +- Admin 部署模板:`.github/workflows/deploy-admin.yml` +- API 镜像构建:`.github/workflows/api-docker-image.yml` + +说明: + +- Web/Admin 工作流通过 Webhook 触发真实部署,需在仓库 Secrets 配置: + - `WEB_DEPLOY_WEBHOOK_URL` + - `ADMIN_DEPLOY_WEBHOOK_URL` +- API 镜像工作流仅在存在 `apps/api/Dockerfile` 时执行镜像构建与推送。 + ## License 本项目遵循 [GNUv3](./LICENSE)。 From 7192cda20feab7fcf7934d47df98bc0863f4eccb Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 14:30:13 +0800 Subject: [PATCH 32/80] fix(api): enable CORS for browser client requests --- apps/api/src/main.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index c7aef94..3f891f4 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -5,6 +5,10 @@ import { AppModule } from "./app.module"; async function bootstrap(): Promise { const app = await NestFactory.create(AppModule); + app.enableCors({ + origin: true, + credentials: true + }); app.useGlobalPipes( new ValidationPipe({ transform: true, From ec1a4f747818c5ca862c82323357949c3540436d Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 14:40:52 +0800 Subject: [PATCH 33/80] feat(api-auth): send email login codes via SMTP --- apps/api/package.json | 1 + apps/api/src/auth/auth-mail.service.ts | 131 +++++++++++++++++++++++++ apps/api/src/auth/auth.controller.ts | 2 +- apps/api/src/auth/auth.module.ts | 3 +- apps/api/src/auth/auth.service.ts | 15 +-- pnpm-lock.yaml | 12 +++ 6 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 apps/api/src/auth/auth-mail.service.ts diff --git a/apps/api/package.json b/apps/api/package.json index 66837c0..b8766b0 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -47,6 +47,7 @@ "@prisma/client": "^7.6.0", "class-transformer": "^0.5.1", "class-validator": "^0.15.1", + "nodemailer": "^8.0.4", "otplib": "^13.4.0", "passport": "^0.7.0", "passport-github2": "^0.1.12", diff --git a/apps/api/src/auth/auth-mail.service.ts b/apps/api/src/auth/auth-mail.service.ts new file mode 100644 index 0000000..b0944bb --- /dev/null +++ b/apps/api/src/auth/auth-mail.service.ts @@ -0,0 +1,131 @@ +import { + Injectable, + InternalServerErrorException, + Logger, + ServiceUnavailableException +} from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { createTransport, type Transporter } from "nodemailer"; + +type MailRuntimeConfig = { + host: string; + port: number; + secure: boolean; + user: string; + pass: string; + fromName: string; + fromAddress: string; +}; + +@Injectable() +export class AuthMailService { + private readonly logger = new Logger(AuthMailService.name); + private cachedConfig: MailRuntimeConfig | null = null; + private transporter: Transporter | null = null; + + constructor(private readonly configService: ConfigService) {} + + async sendLoginCode(email: string, code: string, ttlSeconds: number): Promise { + const config = this.getRuntimeConfig(); + const transporter = this.getTransporter(config); + + try { + await transporter.sendMail({ + from: this.resolveFromField(config), + to: email, + subject: "TodoList 登录验证码", + text: `你的验证码是 ${code},${ttlSeconds} 秒内有效。`, + html: `

你的验证码是 ${code},${ttlSeconds} 秒内有效。

` + }); + } catch (error) { + this.logger.error( + `验证码邮件发送失败: ${email}`, + error instanceof Error ? error.stack : undefined + ); + throw new ServiceUnavailableException("验证码邮件发送失败,请稍后重试"); + } + } + + private getTransporter(config: MailRuntimeConfig): Transporter { + if (this.transporter) { + return this.transporter; + } + + this.transporter = createTransport({ + host: config.host, + port: config.port, + secure: config.secure, + auth: { + user: config.user, + pass: config.pass + } + }); + + return this.transporter; + } + + private getRuntimeConfig(): MailRuntimeConfig { + if (this.cachedConfig) { + return this.cachedConfig; + } + + const host = this.getRequiredString("MAIL_SMTP_HOST"); + const port = this.getRequiredNumber("MAIL_SMTP_PORT"); + const secure = this.getBoolean("MAIL_SMTP_SECURE", port === 465); + const user = this.getRequiredString("MAIL_SMTP_USER"); + const pass = this.getRequiredString("MAIL_SMTP_PASS"); + const fromName = this.configService.get("MAIL_FROM_NAME")?.trim() || "TodoList"; + const fromAddress = this.configService.get("MAIL_FROM_ADDRESS")?.trim() || user; + + const config: MailRuntimeConfig = { + host, + port, + secure, + user, + pass, + fromName, + fromAddress + }; + + this.cachedConfig = config; + return config; + } + + private getRequiredString(key: string): string { + const value = this.configService.get(key)?.trim(); + if (!value) { + throw new InternalServerErrorException(`邮件配置缺失: ${key}`); + } + + return value; + } + + private getRequiredNumber(key: string): number { + const rawValue = this.configService.get(key)?.trim(); + if (!rawValue) { + throw new InternalServerErrorException(`邮件配置缺失: ${key}`); + } + + const parsedValue = Number(rawValue); + if (!Number.isFinite(parsedValue)) { + throw new InternalServerErrorException(`邮件配置格式错误: ${key}`); + } + + return parsedValue; + } + + private getBoolean(key: string, fallback: boolean): boolean { + const rawValue = this.configService.get(key); + if (!rawValue) { + return fallback; + } + + const normalizedValue = rawValue.trim().toLowerCase(); + return normalizedValue === "true" || normalizedValue === "1"; + } + + private resolveFromField(config: MailRuntimeConfig): string { + const sanitizedName = config.fromName.replace(/"/g, ""); + return `"${sanitizedName}" <${config.fromAddress}>`; + } +} diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts index 1641223..707399a 100644 --- a/apps/api/src/auth/auth.controller.ts +++ b/apps/api/src/auth/auth.controller.ts @@ -14,7 +14,7 @@ export class AuthController { @Post("email/send-code") async sendEmailCode( @Body() body: SendEmailCodeDto - ): Promise<{ success: boolean; expiresInSeconds: number; debugCode: string }> { + ): Promise<{ success: boolean; expiresInSeconds: number }> { return this.authService.sendEmailCode(body.email); } diff --git a/apps/api/src/auth/auth.module.ts b/apps/api/src/auth/auth.module.ts index e108ac3..ede59b7 100644 --- a/apps/api/src/auth/auth.module.ts +++ b/apps/api/src/auth/auth.module.ts @@ -3,6 +3,7 @@ import { ConfigModule, ConfigService } from "@nestjs/config"; import { JwtModule } from "@nestjs/jwt"; import { PassportModule } from "@nestjs/passport"; import { AuthController } from "./auth.controller"; +import { AuthMailService } from "./auth-mail.service"; import { AuthService } from "./auth.service"; import { GithubStrategy } from "./strategies/github.strategy"; import { QqStrategy } from "./strategies/qq.strategy"; @@ -27,6 +28,6 @@ import { WechatStrategy } from "./strategies/wechat.strategy"; }) ], controllers: [AuthController], - providers: [AuthService, GithubStrategy, QqStrategy, WechatStrategy] + providers: [AuthService, AuthMailService, GithubStrategy, QqStrategy, WechatStrategy] }) export class AuthModule {} diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index 4e25fa1..11e9223 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -3,6 +3,7 @@ import { ConfigService } from "@nestjs/config"; import { JwtService } from "@nestjs/jwt"; import { randomUUID } from "node:crypto"; import { authenticator } from "@otplib/preset-default"; +import { AuthMailService } from "./auth-mail.service"; type EmailCodeEntry = { code: string; @@ -44,22 +45,22 @@ export class AuthService { constructor( private readonly configService: ConfigService, - private readonly jwtService: JwtService + private readonly jwtService: JwtService, + private readonly authMailService: AuthMailService ) {} - async sendEmailCode( - email: string - ): Promise<{ success: boolean; expiresInSeconds: number; debugCode: string }> { + async sendEmailCode(email: string): Promise<{ success: boolean; expiresInSeconds: number }> { const ttlSeconds = Number(this.configService.get("AUTH_EMAIL_CODE_TTL_SECONDS") ?? 300); const code = this.generateCode(); const expiresAt = Date.now() + ttlSeconds * 1000; + const normalizedEmail = email.toLowerCase(); - this.emailCodeStore.set(email.toLowerCase(), { code, expiresAt }); + await this.authMailService.sendLoginCode(normalizedEmail, code, ttlSeconds); + this.emailCodeStore.set(normalizedEmail, { code, expiresAt }); return { success: true, - expiresInSeconds: ttlSeconds, - debugCode: code + expiresInSeconds: ttlSeconds }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dfba0f7..8b155e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,6 +70,9 @@ importers: class-validator: specifier: ^0.15.1 version: 0.15.1 + nodemailer: + specifier: ^8.0.4 + version: 8.0.4 otplib: specifier: ^13.4.0 version: 13.4.0 @@ -6091,6 +6094,13 @@ packages: integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg== } + nodemailer@8.0.4: + resolution: + { + integrity: sha512-k+jf6N8PfQJ0Fe8ZhJlgqU5qJU44Lpvp2yvidH3vp1lPnVQMgi4yEEMPXg5eJS1gFIJTVq1NHBk7Ia9ARdSBdQ== + } + engines: { node: ">=6.0.0" } + normalize-path@3.0.0: resolution: { @@ -12248,6 +12258,8 @@ snapshots: node-releases@2.0.37: {} + nodemailer@8.0.4: {} + normalize-path@3.0.0: {} npm-run-path@4.0.1: From 352b3c1b3cc9fb7e33850eea01e632b7d9fe3a59 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 14:48:57 +0800 Subject: [PATCH 34/80] feat(api-auth): persist users and auth state in postgres --- apps/api/src/auth/auth.service.ts | 159 +++++++++++++++++++----------- 1 file changed, 102 insertions(+), 57 deletions(-) diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index 11e9223..52bac56 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -4,6 +4,7 @@ import { JwtService } from "@nestjs/jwt"; import { randomUUID } from "node:crypto"; import { authenticator } from "@otplib/preset-default"; import { AuthMailService } from "./auth-mail.service"; +import { PrismaService } from "../prisma/prisma.service"; type EmailCodeEntry = { code: string; @@ -15,17 +16,6 @@ type AuthUser = { email: string; }; -type RefreshTokenEntry = { - userId: string; - expiresAt: number; - revokedAt?: number; -}; - -type TwoFactorEntry = { - secret: string; - enabled: boolean; -}; - type AuthTokenResult = { accessToken: string; tokenType: "Bearer"; @@ -38,15 +28,12 @@ type AuthTokenResult = { @Injectable() export class AuthService { private readonly emailCodeStore = new Map(); - private readonly userStoreByEmail = new Map(); - private readonly userStoreById = new Map(); - private readonly refreshTokenStore = new Map(); - private readonly twoFactorStore = new Map(); constructor( private readonly configService: ConfigService, private readonly jwtService: JwtService, - private readonly authMailService: AuthMailService + private readonly authMailService: AuthMailService, + private readonly prismaService: PrismaService ) {} async sendEmailCode(email: string): Promise<{ success: boolean; expiresInSeconds: number }> { @@ -83,53 +70,92 @@ export class AuthService { this.emailCodeStore.delete(lowerEmail); - const user = this.getOrCreateUser(lowerEmail); + const user = await this.getOrCreateUser(lowerEmail); return this.issueTokens(user); } async refreshTokens(refreshToken: string): Promise { - const entry = this.refreshTokenStore.get(refreshToken); + const entry = await this.prismaService.refreshToken.findUnique({ + where: { + tokenHash: refreshToken + }, + include: { + user: { + select: { + id: true, + email: true + } + } + } + }); + if (!entry) { throw new UnauthorizedException("刷新令牌不存在"); } + if (entry.revokedAt) { throw new UnauthorizedException("刷新令牌已注销"); } - if (entry.expiresAt < Date.now()) { - this.refreshTokenStore.delete(refreshToken); + + if (entry.expiresAt.getTime() < Date.now()) { + await this.prismaService.refreshToken.update({ + where: { + id: entry.id + }, + data: { + revokedAt: new Date() + } + }); throw new UnauthorizedException("刷新令牌已过期"); } - const user = this.userStoreById.get(entry.userId); - if (!user) { - throw new UnauthorizedException("用户不存在"); - } + await this.prismaService.refreshToken.update({ + where: { + id: entry.id + }, + data: { + revokedAt: new Date() + } + }); - entry.revokedAt = Date.now(); - return this.issueTokens(user); + return this.issueTokens(entry.user); } async revokeRefreshToken(refreshToken: string): Promise<{ success: boolean }> { - const entry = this.refreshTokenStore.get(refreshToken); - if (!entry) { - return { success: true }; - } + await this.prismaService.refreshToken.updateMany({ + where: { + tokenHash: refreshToken, + revokedAt: null + }, + data: { + revokedAt: new Date() + } + }); - entry.revokedAt = Date.now(); return { success: true }; } async enrollTwoFactor( email: string ): Promise<{ userId: string; secret: string; otpauthUrl: string; enabled: boolean }> { - const user = this.getOrCreateUser(email.toLowerCase()); + const user = await this.getOrCreateUser(email.toLowerCase()); const secret = authenticator.generateSecret(); const issuer = this.configService.get("AUTH_TOTP_ISSUER") ?? "TodoList"; const otpauthUrl = authenticator.keyuri(user.email, issuer, secret); - this.twoFactorStore.set(user.id, { - secret, - enabled: false + await this.prismaService.userSecurity.upsert({ + where: { + userId: user.id + }, + update: { + twoFactorSecret: secret, + twoFactorEnabled: false + }, + create: { + userId: user.id, + twoFactorSecret: secret, + twoFactorEnabled: false + } }); return { @@ -144,38 +170,54 @@ export class AuthService { email: string, token: string ): Promise<{ success: boolean; enabled: boolean }> { - const user = this.getOrCreateUser(email.toLowerCase()); - const entry = this.twoFactorStore.get(user.id); - if (!entry) { + const user = await this.getOrCreateUser(email.toLowerCase()); + const security = await this.prismaService.userSecurity.findUnique({ + where: { + userId: user.id + }, + select: { + twoFactorSecret: true + } + }); + + if (!security?.twoFactorSecret) { throw new UnauthorizedException("尚未启用两步验证"); } - const valid = authenticator.check(token, entry.secret); + const valid = authenticator.check(token, security.twoFactorSecret); if (!valid) { throw new UnauthorizedException("两步验证码错误"); } - entry.enabled = true; + await this.prismaService.userSecurity.update({ + where: { + userId: user.id + }, + data: { + twoFactorEnabled: true + } + }); + return { success: true, enabled: true }; } - private getOrCreateUser(email: string): AuthUser { - const existingUser = this.userStoreByEmail.get(email); - if (existingUser) { - return existingUser; - } - - const newUser = { - id: randomUUID(), - email - }; - this.userStoreByEmail.set(email, newUser); - this.userStoreById.set(newUser.id, newUser); - - return newUser; + private async getOrCreateUser(email: string): Promise { + return this.prismaService.user.upsert({ + where: { + email + }, + update: {}, + create: { + email + }, + select: { + id: true, + email: true + } + }); } private generateCode(): string { @@ -195,9 +237,12 @@ export class AuthService { }); const refreshToken = `${randomUUID()}${randomUUID()}`; - this.refreshTokenStore.set(refreshToken, { - userId: user.id, - expiresAt: Date.now() + refreshExpiresInSeconds * 1000 + await this.prismaService.refreshToken.create({ + data: { + userId: user.id, + tokenHash: refreshToken, + expiresAt: new Date(Date.now() + refreshExpiresInSeconds * 1000) + } }); return { From 25857abf26607d47575790aa92fc320c5a6e38fb Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 14:57:27 +0800 Subject: [PATCH 35/80] docs(web): localize web README to Chinese --- apps/web/README.md | 108 +++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 62 deletions(-) diff --git a/apps/web/README.md b/apps/web/README.md index 2b4caa6..15e3b1d 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -1,73 +1,57 @@ -# React + TypeScript + Vite +# TodoList Web 前端 -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +这是 TodoList 的用户端前端应用(SPA + PWA),基于 `React + TypeScript + Vite`。 -Currently, two official plugins are available: +## 技术栈 -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) +- React +- TypeScript +- Vite +- Tailwind CSS +- shadcn/ui -## React Compiler +## 本地开发 -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). +在仓库根目录执行: -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default defineConfig([ - globalIgnores(["dist"]), - { - files: ["**/*.{ts,tsx}"], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ["./tsconfig.node.json", "./tsconfig.app.json"], - tsconfigRootDir: import.meta.dirname - } - // other options... - } - } -]); +```bash +pnpm install +pnpm --filter web dev ``` -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: +默认开发地址: -```js -// eslint.config.js -import reactX from "eslint-plugin-react-x"; -import reactDom from "eslint-plugin-react-dom"; +- `http://localhost:5173` -export default defineConfig([ - globalIgnores(["dist"]), - { - files: ["**/*.{ts,tsx}"], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs["recommended-typescript"], - // Enable lint rules for React DOM - reactDom.configs.recommended - ], - languageOptions: { - parserOptions: { - project: ["./tsconfig.node.json", "./tsconfig.app.json"], - tsconfigRootDir: import.meta.dirname - } - // other options... - } - } -]); +## 后端接口地址 + +前端默认请求: + +- `http://localhost:3000` + +如需自定义,请在运行前设置环境变量: + +```bash +VITE_API_BASE_URL=http://localhost:3000 ``` + +## 构建与预览 + +```bash +pnpm --filter web build +pnpm --filter web preview +``` + +## 当前功能进度(阶段性) + +- 邮箱验证码登录页面 +- OAuth 回调页面 +- 会话本地缓存与启动恢复 +- 基础工作台页面骨架 + +## 目录说明 + +- `src/pages`:页面组件 +- `src/components`:通用 UI 组件 +- `src/services`:接口请求与会话处理 +- `src/lib`:工具函数 From 95c10eca776bc48fb27e65d57816a69c84a2a8ce Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 15:05:51 +0800 Subject: [PATCH 36/80] feat(web-auth): implement logout with token revoke and session clear --- apps/web/src/App.tsx | 46 +++++++++++++++++++++++++++++-- apps/web/src/services/auth-api.ts | 25 +++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 9765446..233d2f5 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,10 +1,16 @@ import { useState } from "react"; import { Navigate, Route, Routes, useNavigate } from "react-router-dom"; +import { Button } from "@/components/ui/button"; import { EmailLoginPage } from "@/pages/email-login-page"; import { OAuthCallbackPage } from "@/pages/oauth-callback-page"; import { TodoShellPage } from "@/pages/todo-shell-page"; -import type { EmailLoginResult } from "@/services/auth-api"; -import { loadSession, saveSession, type WebSession } from "@/services/session-storage"; +import { revokeRefreshToken, type EmailLoginResult } from "@/services/auth-api"; +import { + clearSession, + loadSession, + saveSession, + type WebSession +} from "@/services/session-storage"; function toWebSession(payload: EmailLoginResult): WebSession { return { @@ -19,8 +25,27 @@ function toWebSession(payload: EmailLoginResult): WebSession { function App() { const [session, setSession] = useState(() => loadSession()); + const [loggingOut, setLoggingOut] = useState(false); const navigate = useNavigate(); + async function handleLogout(): Promise { + if (!session || loggingOut) { + return; + } + + try { + setLoggingOut(true); + await revokeRefreshToken(session.refreshToken); + } catch { + // 登出流程以本地会话清理为最终兜底,避免页面卡在登录态。 + } finally { + clearSession(); + setSession(null); + setLoggingOut(false); + navigate("/login/email", { replace: true }); + } + } + return (
@@ -29,7 +54,22 @@ function App() {
TodoList
- {session ? session.user.email : "未登录"} + {session ? ( +
+ {session.user.email} + +
+ ) : ( + 未登录 + )}
diff --git a/apps/web/src/services/auth-api.ts b/apps/web/src/services/auth-api.ts index 29a014a..5f2919e 100644 --- a/apps/web/src/services/auth-api.ts +++ b/apps/web/src/services/auth-api.ts @@ -1,4 +1,4 @@ -export type SendEmailCodeResult = { +export type SendEmailCodeResult = { success: boolean; expiresInSeconds: number; }; @@ -15,6 +15,10 @@ export type EmailLoginResult = { }; }; +type RevokeRefreshTokenResult = { + success: boolean; +}; + const DEFAULT_API_BASE_URL = "http://localhost:3000"; function resolveApiBaseUrl(): string { @@ -30,7 +34,7 @@ async function parseErrorMessage(response: Response): Promise { try { const body = (await response.json()) as { message?: string | string[] }; if (Array.isArray(body.message)) { - return body.message.join(";"); + return body.message.join(","); } if (typeof body.message === "string" && body.message.trim()) { return body.message; @@ -75,3 +79,20 @@ export async function loginWithEmailCode(email: string, code: string): Promise { + const response = await fetch(`${resolveApiBaseUrl()}/auth/token/revoke`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ refreshToken }) + }); + + if (!response.ok) { + throw new Error(await parseErrorMessage(response)); + } + + const body = (await response.json()) as RevokeRefreshTokenResult; + return body; +} From e4c20950046ac71c74d66b4c599b80a1145ece7e Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 15:44:13 +0800 Subject: [PATCH 37/80] fix(ci): add nodemailer type declarations for api typecheck --- apps/api/package.json | 1 + pnpm-lock.yaml | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/apps/api/package.json b/apps/api/package.json index b8766b0..f2d2dff 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -20,6 +20,7 @@ "@nestjs/testing": "^11.1.18", "@types/jest": "^30.0.0", "@types/node": "^25.5.2", + "@types/nodemailer": "^8.0.0", "@types/passport-github2": "^1.2.9", "@types/passport-oauth2": "^1.8.0", "@types/supertest": "^7.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b155e7..73e0ebc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,6 +104,9 @@ importers: "@types/node": specifier: ^25.5.2 version: 25.5.2 + "@types/nodemailer": + specifier: ^8.0.0 + version: 8.0.0 "@types/passport-github2": specifier: ^1.2.9 version: 1.2.9 @@ -2708,6 +2711,12 @@ packages: integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg== } + "@types/nodemailer@8.0.0": + resolution: + { + integrity: sha512-fyf8jWULsCo0d0BuoQ75i6IeoHs47qcqxWc7yUdUcV0pOZGjUTTOvwdG1PRXUDqN/8A64yQdQdnA2pZgcdi+cA== + } + "@types/oauth@0.9.6": resolution: { @@ -10166,6 +10175,10 @@ snapshots: dependencies: undici-types: 7.18.2 + "@types/nodemailer@8.0.0": + dependencies: + "@types/node": 25.5.2 + "@types/oauth@0.9.6": dependencies: "@types/node": 25.5.2 From eeee62c4e811917a7093078c52f8e98fe237e182 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 15:47:32 +0800 Subject: [PATCH 38/80] feat(web-ui): use project icon as favicon --- apps/web/index.html | 7 ++++--- apps/web/public/favicon.png | Bin 0 -> 203435 bytes 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 apps/web/public/favicon.png diff --git a/apps/web/index.html b/apps/web/index.html index 5e3836a..da4e35e 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -1,10 +1,11 @@ - + - + + - web + TodoList
diff --git a/apps/web/public/favicon.png b/apps/web/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..0f8c6089efc4dc90da9833d164669320dbf2e9d6 GIT binary patch literal 203435 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

P1^U3Tg+I!&Bk^gyO3AcM#)0MQ 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 z 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 literal 0 HcmV?d00001 From e8dd85ee654ae305914dc7e6741fd5fc6e91c8f8 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sun, 5 Apr 2026 16:57:52 +0800 Subject: [PATCH 39/80] feat(web): polish sidebar shell and authentication UI --- apps/web/src/App.tsx | 318 +++++++++++++++++---- apps/web/src/index.css | 76 ++++- apps/web/src/main.tsx | 3 + apps/web/src/pages/email-login-page.tsx | 70 +++-- apps/web/src/pages/oauth-callback-page.tsx | 11 +- apps/web/src/pages/todo-shell-page.tsx | 23 +- apps/web/src/services/theme-storage.ts | 29 ++ apps/web/tailwind.config.js | 37 ++- 8 files changed, 466 insertions(+), 101 deletions(-) create mode 100644 apps/web/src/services/theme-storage.ts diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 233d2f5..2fc19b0 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,6 +1,22 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; +import type { LucideIcon } from "lucide-react"; +import { + Bell, + ChevronLeft, + ChevronRight, + LayoutDashboard, + ListTodo, + LogOut, + Menu, + Moon, + Settings, + Sparkles, + Sun, + X +} from "lucide-react"; import { Navigate, Route, Routes, useNavigate } from "react-router-dom"; import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; import { EmailLoginPage } from "@/pages/email-login-page"; import { OAuthCallbackPage } from "@/pages/oauth-callback-page"; import { TodoShellPage } from "@/pages/todo-shell-page"; @@ -11,6 +27,26 @@ import { saveSession, type WebSession } from "@/services/session-storage"; +import { + applyThemeMode, + loadThemeMode, + saveThemeMode, + type ThemeMode +} from "@/services/theme-storage"; + +type SidebarItem = { + key: string; + label: string; + icon: LucideIcon; +}; + +const SIDEBAR_ITEMS: SidebarItem[] = [ + { key: "dashboard", label: "概览面板", icon: LayoutDashboard }, + { key: "todo", label: "待办事项", icon: ListTodo }, + { key: "ai", label: "AI 建议", icon: Sparkles }, + { key: "notice", label: "提醒中心", icon: Bell }, + { key: "settings", label: "系统设置", icon: Settings } +]; function toWebSession(payload: EmailLoginResult): WebSession { return { @@ -26,8 +62,16 @@ function toWebSession(payload: EmailLoginResult): WebSession { function App() { const [session, setSession] = useState(() => loadSession()); const [loggingOut, setLoggingOut] = useState(false); + const [themeMode, setThemeMode] = useState(() => loadThemeMode()); + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); + const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); const navigate = useNavigate(); + useEffect(() => { + applyThemeMode(themeMode); + saveThemeMode(themeMode); + }, [themeMode]); + async function handleLogout(): Promise { if (!session || loggingOut) { return; @@ -37,75 +81,233 @@ function App() { setLoggingOut(true); await revokeRefreshToken(session.refreshToken); } catch { - // 登出流程以本地会话清理为最终兜底,避免页面卡在登录态。 + // 无论接口成功与否,都要清理本地会话,避免页面卡在登录态。 } finally { clearSession(); setSession(null); setLoggingOut(false); + setMobileSidebarOpen(false); navigate("/login/email", { replace: true }); } } - return ( -

-
-
-
-
- TodoList + function handleToggleTheme(): void { + setThemeMode((currentTheme) => (currentTheme === "dark" ? "light" : "dark")); + } + + function renderSidebarContent(options: { collapsed: boolean; mobile: boolean }) { + const { collapsed, mobile } = options; + + return ( +
+ {mobile ? ( +
+
- {session ? ( -
- {session.user.email} - + ) : null} + +
+ +
+ +
+ + + +
+
+ ); + } + + return ( +
+
+
+
+ + TodoList +
+ + TodoList +
- ) : ( - 未登录 - )} +
+ + {session ? session.user.email : "未登录"} +
-
- - { - const nextSession = toWebSession(payload); - saveSession(nextSession); - setSession(nextSession); - navigate("/"); - }} - /> - } - /> - { - setSession(nextSession); - }} - /> - } - /> - : - } - /> - } /> - -
+ + {mobileSidebarOpen ? ( + + + +
+
+
+ + { + const nextSession = toWebSession(payload); + saveSession(nextSession); + setSession(nextSession); + setMobileSidebarOpen(false); + navigate("/"); + }} + /> + } + /> + { + setSession(nextSession); + setMobileSidebarOpen(false); + }} + /> + } + /> + + ) : ( + + ) + } + /> + } + /> + +
+
+
+
); } diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 3239bed..8faabac 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1,21 +1,77 @@ -@import "@fontsource-variable/geist"; +@import "@fontsource-variable/geist"; @tailwind base; @tailwind components; @tailwind utilities; +html { + height: 100%; + font-size: 120%; +} + :root { - --radius: 0.625rem; - --background: #f6f8f7; - --foreground: #122117; - --primary: #0a7a5a; - --primary-foreground: #ffffff; - --border: #d7e2db; + --radius: 0.9rem; + --background: 246 100% 98%; + --foreground: 248 40% 18%; + --card: 0 0% 100%; + --border: 248 53% 89%; + --input: 248 48% 84%; + --ring: 246 53% 53%; + --primary: 246 53% 53%; + --primary-foreground: 0 0% 100%; + --secondary: 250 70% 94%; + --secondary-foreground: 248 40% 25%; + --muted: 249 78% 95%; + --muted-foreground: 248 17% 45%; + --accent: 173 56% 66%; + --accent-foreground: 248 40% 25%; + --destructive: 343 40% 50%; + --destructive-foreground: 0 0% 100%; + color-scheme: light; font-family: "Geist Variable", "Noto Sans SC", sans-serif; } +.dark { + --background: 248 46% 10%; + --foreground: 240 44% 96%; + --card: 249 41% 15%; + --border: 249 24% 30%; + --input: 249 23% 28%; + --ring: 246 76% 68%; + --primary: 246 76% 68%; + --primary-foreground: 248 43% 12%; + --secondary: 249 30% 24%; + --secondary-foreground: 240 38% 92%; + --muted: 249 29% 20%; + --muted-foreground: 247 16% 74%; + --accent: 173 52% 58%; + --accent-foreground: 248 43% 12%; + --destructive: 343 64% 58%; + --destructive-foreground: 0 0% 100%; + color-scheme: dark; +} + body { margin: 0; - min-height: 100vh; - background: var(--background); - color: var(--foreground); + height: 100%; + overflow: hidden; + background: + radial-gradient( + 1100px 620px at -12% -25%, + hsl(var(--primary) / 0.2), + hsl(var(--primary) / 0) 60% + ), + radial-gradient( + 900px 540px at 112% -14%, + hsl(var(--accent) / 0.35), + hsl(var(--accent) / 0) 58% + ), + hsl(var(--background)); + color: hsl(var(--foreground)); + transition: + background-color 220ms ease, + color 220ms ease; +} + +#root { + height: 100%; } diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 85a16d5..366221b 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -3,6 +3,9 @@ import { createRoot } from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; import "./index.css"; import App from "./App.tsx"; +import { applyThemeMode, loadThemeMode } from "@/services/theme-storage"; + +applyThemeMode(loadThemeMode()); createRoot(document.getElementById("root")!).render( diff --git a/apps/web/src/pages/email-login-page.tsx b/apps/web/src/pages/email-login-page.tsx index fe17509..7da5a46 100644 --- a/apps/web/src/pages/email-login-page.tsx +++ b/apps/web/src/pages/email-login-page.tsx @@ -84,31 +84,39 @@ export function EmailLoginPage({ onLoginSuccess }: EmailLoginPageProps) { } return ( -
-

邮箱验证码登录

-

- 输入邮箱后获取验证码,再完成登录。你也可以直接使用第三方账号登录。 -

+
+
+ TodoList +
+

邮箱验证码登录

+
+
-
); } diff --git a/apps/web/src/pages/oauth-callback-page.tsx b/apps/web/src/pages/oauth-callback-page.tsx index f4ae5f3..e369704 100644 --- a/apps/web/src/pages/oauth-callback-page.tsx +++ b/apps/web/src/pages/oauth-callback-page.tsx @@ -49,13 +49,16 @@ export function OAuthCallbackPage({ onBootstrapSession }: OAuthCallbackPageProps } return ( -
-

OAuth 回调处理中

-

+

+
+ TodoList +

OAuth 回调处理中

+
+

{parseResult.ok ? "已收到回调参数,点击继续进入工作台。" : parseResult.reason}

+
+ + {taskList.length === 0 ? ( +

+ 还没有任务,点击右上角“新建任务”。 +

+ ) : ( +
+ {taskList.map((task) => { + const isActive = task.id === selectedTaskId; + return ( + + ); + })} +
+ )} + + +
+
+

任务详情

+
+ + +
+
+ + {!selectedTaskId || !selectedTask ? ( +

+ 请选择一个任务进行编辑。 +

+ ) : ( +
+ + +
+ + + +
+ + + +