ai-generated junk tool for migrating atproto identities in-browser

Compare changes

Choose any two refs to compare.

+38
.github/workflows/sync-tangled.yaml
···
··· 1 + name: sync-tangled 2 + 3 + on: 4 + push: 5 + 6 + jobs: 7 + sync: 8 + name: ${{ matrix.name }} 9 + runs-on: ubuntu-latest 10 + steps: 11 + - uses: actions/checkout@v4.1.7 12 + with: 13 + fetch-depth: 0 14 + ref: ${{ github.event.pull_request.head.sha }} 15 + - name: sync tangled 16 + env: 17 + TANGLED_SSH_KEY: ${{ secrets.TANGLED_SSH_KEY }} 18 + run: | 19 + set -euo pipefail 20 + # Turn off strict SSH key checking 21 + mkdir -p ~/.ssh 22 + echo "Host * 23 + StrictHostKeyChecking no 24 + UserKnownHostsFile=/dev/null" > ~/.ssh/config 25 + 26 + # Write SSH key to disk 27 + echo "$TANGLED_SSH_KEY" > ~/.ssh/tangled_key 28 + chmod 600 ~/.ssh/tangled_key 29 + 30 + # Configure SSH to use the key for tangled.sh 31 + echo "Host tangled.sh 32 + IdentityFile ~/.ssh/tangled_key" >> ~/.ssh/config 33 + 34 + chmod 600 ~/.ssh/config 35 + 36 + git remote add tangled git@tangled.sh:noob.quest/atproto-migrator 37 + git push -f --all tangled 38 + git push -f --tags tangled
+1
.nvmrc
···
··· 1 + v24.1.0
+4 -2
README.md
··· 1 # atproto-migrator 2 - basic (NON-FUNCTIONAL) web application that allows for an user to migrate their bluesky/at protocol account to another PDS without using command line tools. 3 4 [live version available here](https://atproto-migrator.pages.dev) 5 6 - ## install 7 8 ```bash 9 npm install ··· 15 ```bash 16 npm run build 17 ``` 18 19 open the `dist` directory and you're set
··· 1 # atproto-migrator 2 + basic (NON-FUNCTIONAL) web application that allows for an user to migrate their bluesky/at protocol account to another PDS without using command line tools, as well as some other repo stuff 3 4 [live version available here](https://atproto-migrator.pages.dev) 5 6 + ## run for developing 7 8 ```bash 9 npm install ··· 15 ```bash 16 npm run build 17 ``` 18 + 19 + if you type ```ANALYZE=1``` you will receive a bundle analysis, useful for optimizing 20 21 open the `dist` directory and you're set
-28
eslint.config.js
··· 1 - import js from '@eslint/js' 2 - import globals from 'globals' 3 - import reactHooks from 'eslint-plugin-react-hooks' 4 - import reactRefresh from 'eslint-plugin-react-refresh' 5 - import tseslint from 'typescript-eslint' 6 - 7 - export default tseslint.config( 8 - { ignores: ['dist'] }, 9 - { 10 - extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 - files: ['**/*.{ts,tsx}'], 12 - languageOptions: { 13 - ecmaVersion: 2020, 14 - globals: globals.browser, 15 - }, 16 - plugins: { 17 - 'react-hooks': reactHooks, 18 - 'react-refresh': reactRefresh, 19 - }, 20 - rules: { 21 - ...reactHooks.configs.recommended.rules, 22 - 'react-refresh/only-export-components': [ 23 - 'warn', 24 - { allowConstantExport: true }, 25 - ], 26 - }, 27 - }, 28 - )
···
+40
eslint.config.ts
···
··· 1 + import { FlatCompat } from '@eslint/eslintrc' 2 + import js from '@eslint/js' 3 + import * as path from 'node:path' 4 + import { fileURLToPath } from 'node:url' 5 + import tseslint from 'typescript-eslint' 6 + 7 + const __filename = fileURLToPath(import.meta.url) 8 + const __dirname = path.dirname(__filename) 9 + 10 + const compat = new FlatCompat({ 11 + baseDirectory: __dirname, 12 + recommendedConfig: js.configs.recommended 13 + }) 14 + 15 + export default tseslint.config( 16 + ...compat.config({ 17 + root: true, 18 + extends: [ 19 + 'eslint:recommended', 20 + 'plugin:@typescript-eslint/recommended', 21 + 'plugin:react-hooks/recommended' 22 + ], 23 + parser: '@typescript-eslint/parser', 24 + parserOptions: { 25 + project: ['./tsconfig.app.json'], 26 + tsconfigRootDir: __dirname, 27 + }, 28 + plugins: ['@typescript-eslint', 'react-refresh'], 29 + rules: { 30 + 'react-refresh/only-export-components': [ 31 + 'warn', 32 + { allowConstantExport: true } 33 + ], 34 + '@typescript-eslint/no-deprecated': 'error' 35 + } 36 + }), 37 + { 38 + ignores: ['dist/**/*'] 39 + } 40 + )
+2 -2
index.html
··· 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 - <link rel="icon" type="image/svg+xml" href="/vite.svg" /> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 <title>ATproto Migrator</title> 8 </head> ··· 41 <div id="root"></div> 42 <script type="module" src="/src/main.tsx"></script> 43 </body> 44 - </html>
··· 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 + <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 <title>ATproto Migrator</title> 8 </head> ··· 41 <div id="root"></div> 42 <script type="module" src="/src/main.tsx"></script> 43 </body> 44 + </html>
+893 -1309
package-lock.json
··· 1 { 2 "name": "atproto-migration", 3 - "version": "0.0.0", 4 "lockfileVersion": 3, 5 "requires": true, 6 "packages": { 7 "": { 8 "name": "atproto-migration", 9 - "version": "0.0.0", 10 "dependencies": { 11 "@atproto/api": "^0.15.5", 12 "react": "^19.0.0", 13 "react-dom": "^19.0.0", 14 - "react-router-dom": "^7.5.3" 15 }, 16 "devDependencies": { 17 "@eslint/js": "^9.22.0", 18 - "@types/react": "^19.0.10", 19 - "@types/react-dom": "^19.0.4", 20 "@vitejs/plugin-react": "^4.3.4", 21 "eslint": "^9.22.0", 22 "eslint-plugin-react-hooks": "^5.2.0", 23 "eslint-plugin-react-refresh": "^0.4.19", 24 "globals": "^16.0.0", 25 "typescript": "~5.7.2", 26 "typescript-eslint": "^8.26.1", 27 "vite": "^6.3.1" ··· 42 } 43 }, 44 "node_modules/@atproto/api": { 45 - "version": "0.15.5", 46 - "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.15.5.tgz", 47 - "integrity": "sha512-GiKOrjSXMm8OSpc+pfjFTBYQGX62jmorECkTx2VZbS6KtFKFY0cRQAI+JnQoOLF/8TvzpaAZB7+it73uIqDM7A==", 48 "license": "MIT", 49 "dependencies": { 50 - "@atproto/common-web": "^0.4.1", 51 - "@atproto/lexicon": "^0.4.10", 52 "@atproto/syntax": "^0.4.0", 53 - "@atproto/xrpc": "^0.6.12", 54 "await-lock": "^2.2.2", 55 "multiformats": "^9.9.0", 56 "tlds": "^1.234.0", 57 "zod": "^3.23.8" 58 } 59 }, 60 "node_modules/@atproto/common-web": { 61 - "version": "0.4.1", 62 - "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.1.tgz", 63 - "integrity": "sha512-Ghh+djHYMAUCktLKwr2IuGgtjcwSWGudp+K7+N7KBA9pDDloOXUEY8Agjc5SHSo9B1QIEFkegClU5n+apn2e0w==", 64 "license": "MIT", 65 "dependencies": { 66 "graphemer": "^1.4.0", ··· 69 "zod": "^3.23.8" 70 } 71 }, 72 "node_modules/@atproto/lexicon": { 73 - "version": "0.4.10", 74 - "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.10.tgz", 75 - "integrity": "sha512-uDbP20vetBgtXPuxoyRcvOGBt2gNe1dFc9yYKcb6jWmXfseHiGTnIlORJOLBXIT2Pz15Eap4fLxAu6zFAykD5A==", 76 "license": "MIT", 77 "dependencies": { 78 - "@atproto/common-web": "^0.4.1", 79 "@atproto/syntax": "^0.4.0", 80 "iso-datestring-validator": "^2.2.2", 81 "multiformats": "^9.9.0", 82 "zod": "^3.23.8" 83 } 84 }, 85 "node_modules/@atproto/syntax": { 86 "version": "0.4.0", 87 "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.0.tgz", ··· 89 "license": "MIT" 90 }, 91 "node_modules/@atproto/xrpc": { 92 - "version": "0.6.12", 93 - "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.6.12.tgz", 94 - "integrity": "sha512-Ut3iISNLujlmY9Gu8sNU+SPDJDvqlVzWddU8qUr0Yae5oD4SguaUFjjhireMGhQ3M5E0KljQgDbTmnBo1kIZ3w==", 95 "license": "MIT", 96 "dependencies": { 97 - "@atproto/lexicon": "^0.4.10", 98 "zod": "^3.23.8" 99 } 100 }, ··· 114 } 115 }, 116 "node_modules/@babel/compat-data": { 117 - "version": "7.27.1", 118 - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.1.tgz", 119 - "integrity": "sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A==", 120 "dev": true, 121 "license": "MIT", 122 "engines": { ··· 124 } 125 }, 126 "node_modules/@babel/core": { 127 - "version": "7.27.1", 128 - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", 129 - "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", 130 "dev": true, 131 "license": "MIT", 132 "dependencies": { 133 "@ampproject/remapping": "^2.2.0", 134 "@babel/code-frame": "^7.27.1", 135 - "@babel/generator": "^7.27.1", 136 - "@babel/helper-compilation-targets": "^7.27.1", 137 - "@babel/helper-module-transforms": "^7.27.1", 138 - "@babel/helpers": "^7.27.1", 139 - "@babel/parser": "^7.27.1", 140 - "@babel/template": "^7.27.1", 141 - "@babel/traverse": "^7.27.1", 142 - "@babel/types": "^7.27.1", 143 "convert-source-map": "^2.0.0", 144 "debug": "^4.1.0", 145 "gensync": "^1.0.0-beta.2", ··· 154 "url": "https://opencollective.com/babel" 155 } 156 }, 157 "node_modules/@babel/generator": { 158 - "version": "7.27.1", 159 - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", 160 - "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", 161 "dev": true, 162 "license": "MIT", 163 "dependencies": { 164 - "@babel/parser": "^7.27.1", 165 - "@babel/types": "^7.27.1", 166 "@jridgewell/gen-mapping": "^0.3.5", 167 "@jridgewell/trace-mapping": "^0.3.25", 168 "jsesc": "^3.0.2" ··· 172 } 173 }, 174 "node_modules/@babel/helper-compilation-targets": { 175 - "version": "7.27.1", 176 - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.1.tgz", 177 - "integrity": "sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==", 178 "dev": true, 179 "license": "MIT", 180 "dependencies": { 181 - "@babel/compat-data": "^7.27.1", 182 "@babel/helper-validator-option": "^7.27.1", 183 "browserslist": "^4.24.0", 184 "lru-cache": "^5.1.1", ··· 188 "node": ">=6.9.0" 189 } 190 }, 191 "node_modules/@babel/helper-module-imports": { 192 "version": "7.27.1", 193 "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", ··· 203 } 204 }, 205 "node_modules/@babel/helper-module-transforms": { 206 - "version": "7.27.1", 207 - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", 208 - "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", 209 "dev": true, 210 "license": "MIT", 211 "dependencies": { 212 "@babel/helper-module-imports": "^7.27.1", 213 "@babel/helper-validator-identifier": "^7.27.1", 214 - "@babel/traverse": "^7.27.1" 215 }, 216 "engines": { 217 "node": ">=6.9.0" ··· 261 } 262 }, 263 "node_modules/@babel/helpers": { 264 - "version": "7.27.1", 265 - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", 266 - "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", 267 "dev": true, 268 "license": "MIT", 269 "dependencies": { 270 - "@babel/template": "^7.27.1", 271 - "@babel/types": "^7.27.1" 272 }, 273 "engines": { 274 "node": ">=6.9.0" 275 } 276 }, 277 "node_modules/@babel/parser": { 278 - "version": "7.27.1", 279 - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz", 280 - "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==", 281 "dev": true, 282 "license": "MIT", 283 "dependencies": { 284 - "@babel/types": "^7.27.1" 285 }, 286 "bin": { 287 "parser": "bin/babel-parser.js" ··· 323 } 324 }, 325 "node_modules/@babel/template": { 326 - "version": "7.27.1", 327 - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz", 328 - "integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==", 329 "dev": true, 330 "license": "MIT", 331 "dependencies": { 332 "@babel/code-frame": "^7.27.1", 333 - "@babel/parser": "^7.27.1", 334 "@babel/types": "^7.27.1" 335 }, 336 "engines": { ··· 338 } 339 }, 340 "node_modules/@babel/traverse": { 341 - "version": "7.27.1", 342 - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", 343 - "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", 344 "dev": true, 345 "license": "MIT", 346 "dependencies": { 347 "@babel/code-frame": "^7.27.1", 348 - "@babel/generator": "^7.27.1", 349 - "@babel/parser": "^7.27.1", 350 - "@babel/template": "^7.27.1", 351 - "@babel/types": "^7.27.1", 352 "debug": "^4.3.1", 353 "globals": "^11.1.0" 354 }, ··· 367 } 368 }, 369 "node_modules/@babel/types": { 370 - "version": "7.27.1", 371 - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", 372 - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", 373 "dev": true, 374 "license": "MIT", 375 "dependencies": { ··· 381 } 382 }, 383 "node_modules/@esbuild/aix-ppc64": { 384 - "version": "0.25.3", 385 - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", 386 - "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", 387 "cpu": [ 388 "ppc64" 389 ], ··· 398 } 399 }, 400 "node_modules/@esbuild/android-arm": { 401 - "version": "0.25.3", 402 - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", 403 - "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", 404 "cpu": [ 405 "arm" 406 ], ··· 415 } 416 }, 417 "node_modules/@esbuild/android-arm64": { 418 - "version": "0.25.3", 419 - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", 420 - "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", 421 "cpu": [ 422 "arm64" 423 ], ··· 432 } 433 }, 434 "node_modules/@esbuild/android-x64": { 435 - "version": "0.25.3", 436 - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", 437 - "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", 438 "cpu": [ 439 "x64" 440 ], ··· 449 } 450 }, 451 "node_modules/@esbuild/darwin-arm64": { 452 - "version": "0.25.3", 453 - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", 454 - "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", 455 "cpu": [ 456 "arm64" 457 ], ··· 466 } 467 }, 468 "node_modules/@esbuild/darwin-x64": { 469 - "version": "0.25.3", 470 - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", 471 - "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", 472 "cpu": [ 473 "x64" 474 ], ··· 483 } 484 }, 485 "node_modules/@esbuild/freebsd-arm64": { 486 - "version": "0.25.3", 487 - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", 488 - "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", 489 "cpu": [ 490 "arm64" 491 ], ··· 500 } 501 }, 502 "node_modules/@esbuild/freebsd-x64": { 503 - "version": "0.25.3", 504 - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", 505 - "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", 506 "cpu": [ 507 "x64" 508 ], ··· 517 } 518 }, 519 "node_modules/@esbuild/linux-arm": { 520 - "version": "0.25.3", 521 - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", 522 - "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", 523 "cpu": [ 524 "arm" 525 ], ··· 534 } 535 }, 536 "node_modules/@esbuild/linux-arm64": { 537 - "version": "0.25.3", 538 - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", 539 - "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", 540 "cpu": [ 541 "arm64" 542 ], ··· 551 } 552 }, 553 "node_modules/@esbuild/linux-ia32": { 554 - "version": "0.25.3", 555 - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", 556 - "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", 557 "cpu": [ 558 "ia32" 559 ], ··· 568 } 569 }, 570 "node_modules/@esbuild/linux-loong64": { 571 - "version": "0.25.3", 572 - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", 573 - "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", 574 "cpu": [ 575 "loong64" 576 ], ··· 585 } 586 }, 587 "node_modules/@esbuild/linux-mips64el": { 588 - "version": "0.25.3", 589 - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", 590 - "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", 591 "cpu": [ 592 "mips64el" 593 ], ··· 602 } 603 }, 604 "node_modules/@esbuild/linux-ppc64": { 605 - "version": "0.25.3", 606 - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", 607 - "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", 608 "cpu": [ 609 "ppc64" 610 ], ··· 619 } 620 }, 621 "node_modules/@esbuild/linux-riscv64": { 622 - "version": "0.25.3", 623 - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", 624 - "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", 625 "cpu": [ 626 "riscv64" 627 ], ··· 636 } 637 }, 638 "node_modules/@esbuild/linux-s390x": { 639 - "version": "0.25.3", 640 - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", 641 - "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", 642 "cpu": [ 643 "s390x" 644 ], ··· 653 } 654 }, 655 "node_modules/@esbuild/linux-x64": { 656 - "version": "0.25.3", 657 - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", 658 - "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", 659 "cpu": [ 660 "x64" 661 ], ··· 670 } 671 }, 672 "node_modules/@esbuild/netbsd-arm64": { 673 - "version": "0.25.3", 674 - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", 675 - "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", 676 "cpu": [ 677 "arm64" 678 ], ··· 687 } 688 }, 689 "node_modules/@esbuild/netbsd-x64": { 690 - "version": "0.25.3", 691 - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", 692 - "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", 693 "cpu": [ 694 "x64" 695 ], ··· 704 } 705 }, 706 "node_modules/@esbuild/openbsd-arm64": { 707 - "version": "0.25.3", 708 - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", 709 - "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", 710 "cpu": [ 711 "arm64" 712 ], ··· 721 } 722 }, 723 "node_modules/@esbuild/openbsd-x64": { 724 - "version": "0.25.3", 725 - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", 726 - "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", 727 "cpu": [ 728 "x64" 729 ], ··· 738 } 739 }, 740 "node_modules/@esbuild/sunos-x64": { 741 - "version": "0.25.3", 742 - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", 743 - "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", 744 "cpu": [ 745 "x64" 746 ], ··· 755 } 756 }, 757 "node_modules/@esbuild/win32-arm64": { 758 - "version": "0.25.3", 759 - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", 760 - "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", 761 "cpu": [ 762 "arm64" 763 ], ··· 772 } 773 }, 774 "node_modules/@esbuild/win32-ia32": { 775 - "version": "0.25.3", 776 - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", 777 - "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", 778 "cpu": [ 779 "ia32" 780 ], ··· 789 } 790 }, 791 "node_modules/@esbuild/win32-x64": { 792 - "version": "0.25.3", 793 - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", 794 - "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", 795 "cpu": [ 796 "x64" 797 ], ··· 824 "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 825 } 826 }, 827 - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 828 - "version": "3.4.3", 829 - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 830 - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 831 - "dev": true, 832 - "license": "Apache-2.0", 833 - "engines": { 834 - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 835 - }, 836 - "funding": { 837 - "url": "https://opencollective.com/eslint" 838 - } 839 - }, 840 "node_modules/@eslint-community/regexpp": { 841 "version": "4.12.1", 842 "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", ··· 873 } 874 }, 875 "node_modules/@eslint/core": { 876 - "version": "0.13.0", 877 - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", 878 - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", 879 "dev": true, 880 "license": "Apache-2.0", 881 "dependencies": { ··· 923 } 924 }, 925 "node_modules/@eslint/js": { 926 - "version": "9.26.0", 927 - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", 928 - "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", 929 "dev": true, 930 "license": "MIT", 931 "engines": { 932 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 933 } 934 }, 935 "node_modules/@eslint/object-schema": { ··· 943 } 944 }, 945 "node_modules/@eslint/plugin-kit": { 946 - "version": "0.2.8", 947 - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", 948 - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", 949 "dev": true, 950 "license": "Apache-2.0", 951 "dependencies": { 952 - "@eslint/core": "^0.13.0", 953 "levn": "^0.4.1" 954 }, 955 "engines": { ··· 1009 } 1010 }, 1011 "node_modules/@humanwhocodes/retry": { 1012 - "version": "0.4.2", 1013 - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", 1014 - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", 1015 "dev": true, 1016 "license": "Apache-2.0", 1017 "engines": { ··· 1057 "node": ">=6.0.0" 1058 } 1059 }, 1060 "node_modules/@jridgewell/sourcemap-codec": { 1061 "version": "1.5.0", 1062 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", ··· 1075 "@jridgewell/sourcemap-codec": "^1.4.14" 1076 } 1077 }, 1078 - "node_modules/@modelcontextprotocol/sdk": { 1079 - "version": "1.11.0", 1080 - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.0.tgz", 1081 - "integrity": "sha512-k/1pb70eD638anoi0e8wUGAlbMJXyvdV4p62Ko+EZ7eBe1xMx8Uhak1R5DgfoofsK5IBBnRwsYGTaLZl+6/+RQ==", 1082 - "dev": true, 1083 "license": "MIT", 1084 "dependencies": { 1085 - "content-type": "^1.0.5", 1086 - "cors": "^2.8.5", 1087 - "cross-spawn": "^7.0.3", 1088 - "eventsource": "^3.0.2", 1089 - "express": "^5.0.1", 1090 - "express-rate-limit": "^7.5.0", 1091 - "pkce-challenge": "^5.0.0", 1092 - "raw-body": "^3.0.0", 1093 - "zod": "^3.23.8", 1094 - "zod-to-json-schema": "^3.24.1" 1095 }, 1096 "engines": { 1097 - "node": ">=18" 1098 } 1099 }, 1100 "node_modules/@nodelib/fs.scandir": { ··· 1135 "node": ">= 8" 1136 } 1137 }, 1138 "node_modules/@rollup/rollup-android-arm-eabi": { 1139 - "version": "4.40.1", 1140 - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz", 1141 - "integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==", 1142 "cpu": [ 1143 "arm" 1144 ], ··· 1150 ] 1151 }, 1152 "node_modules/@rollup/rollup-android-arm64": { 1153 - "version": "4.40.1", 1154 - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz", 1155 - "integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==", 1156 "cpu": [ 1157 "arm64" 1158 ], ··· 1164 ] 1165 }, 1166 "node_modules/@rollup/rollup-darwin-arm64": { 1167 - "version": "4.40.1", 1168 - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz", 1169 - "integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==", 1170 "cpu": [ 1171 "arm64" 1172 ], ··· 1178 ] 1179 }, 1180 "node_modules/@rollup/rollup-darwin-x64": { 1181 - "version": "4.40.1", 1182 - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz", 1183 - "integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==", 1184 "cpu": [ 1185 "x64" 1186 ], ··· 1192 ] 1193 }, 1194 "node_modules/@rollup/rollup-freebsd-arm64": { 1195 - "version": "4.40.1", 1196 - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz", 1197 - "integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==", 1198 "cpu": [ 1199 "arm64" 1200 ], ··· 1206 ] 1207 }, 1208 "node_modules/@rollup/rollup-freebsd-x64": { 1209 - "version": "4.40.1", 1210 - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz", 1211 - "integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==", 1212 "cpu": [ 1213 "x64" 1214 ], ··· 1220 ] 1221 }, 1222 "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 1223 - "version": "4.40.1", 1224 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz", 1225 - "integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==", 1226 "cpu": [ 1227 "arm" 1228 ], ··· 1234 ] 1235 }, 1236 "node_modules/@rollup/rollup-linux-arm-musleabihf": { 1237 - "version": "4.40.1", 1238 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz", 1239 - "integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==", 1240 "cpu": [ 1241 "arm" 1242 ], ··· 1248 ] 1249 }, 1250 "node_modules/@rollup/rollup-linux-arm64-gnu": { 1251 - "version": "4.40.1", 1252 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz", 1253 - "integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==", 1254 "cpu": [ 1255 "arm64" 1256 ], ··· 1262 ] 1263 }, 1264 "node_modules/@rollup/rollup-linux-arm64-musl": { 1265 - "version": "4.40.1", 1266 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz", 1267 - "integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==", 1268 "cpu": [ 1269 "arm64" 1270 ], ··· 1276 ] 1277 }, 1278 "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 1279 - "version": "4.40.1", 1280 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz", 1281 - "integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==", 1282 "cpu": [ 1283 "loong64" 1284 ], ··· 1290 ] 1291 }, 1292 "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 1293 - "version": "4.40.1", 1294 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz", 1295 - "integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==", 1296 "cpu": [ 1297 "ppc64" 1298 ], ··· 1304 ] 1305 }, 1306 "node_modules/@rollup/rollup-linux-riscv64-gnu": { 1307 - "version": "4.40.1", 1308 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz", 1309 - "integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==", 1310 "cpu": [ 1311 "riscv64" 1312 ], ··· 1318 ] 1319 }, 1320 "node_modules/@rollup/rollup-linux-riscv64-musl": { 1321 - "version": "4.40.1", 1322 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz", 1323 - "integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==", 1324 "cpu": [ 1325 "riscv64" 1326 ], ··· 1332 ] 1333 }, 1334 "node_modules/@rollup/rollup-linux-s390x-gnu": { 1335 - "version": "4.40.1", 1336 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz", 1337 - "integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==", 1338 "cpu": [ 1339 "s390x" 1340 ], ··· 1346 ] 1347 }, 1348 "node_modules/@rollup/rollup-linux-x64-gnu": { 1349 - "version": "4.40.1", 1350 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz", 1351 - "integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==", 1352 "cpu": [ 1353 "x64" 1354 ], ··· 1360 ] 1361 }, 1362 "node_modules/@rollup/rollup-linux-x64-musl": { 1363 - "version": "4.40.1", 1364 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz", 1365 - "integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==", 1366 "cpu": [ 1367 "x64" 1368 ], ··· 1374 ] 1375 }, 1376 "node_modules/@rollup/rollup-win32-arm64-msvc": { 1377 - "version": "4.40.1", 1378 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz", 1379 - "integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==", 1380 "cpu": [ 1381 "arm64" 1382 ], ··· 1388 ] 1389 }, 1390 "node_modules/@rollup/rollup-win32-ia32-msvc": { 1391 - "version": "4.40.1", 1392 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz", 1393 - "integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==", 1394 "cpu": [ 1395 "ia32" 1396 ], ··· 1402 ] 1403 }, 1404 "node_modules/@rollup/rollup-win32-x64-msvc": { 1405 - "version": "4.40.1", 1406 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz", 1407 - "integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==", 1408 "cpu": [ 1409 "x64" 1410 ], ··· 1474 "dev": true, 1475 "license": "MIT" 1476 }, 1477 "node_modules/@types/react": { 1478 - "version": "19.1.2", 1479 - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", 1480 - "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", 1481 "dev": true, 1482 "license": "MIT", 1483 "dependencies": { ··· 1485 } 1486 }, 1487 "node_modules/@types/react-dom": { 1488 - "version": "19.1.3", 1489 - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.3.tgz", 1490 - "integrity": "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==", 1491 "dev": true, 1492 "license": "MIT", 1493 "peerDependencies": { ··· 1495 } 1496 }, 1497 "node_modules/@typescript-eslint/eslint-plugin": { 1498 - "version": "8.31.1", 1499 - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz", 1500 - "integrity": "sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ==", 1501 "dev": true, 1502 "license": "MIT", 1503 "dependencies": { 1504 "@eslint-community/regexpp": "^4.10.0", 1505 - "@typescript-eslint/scope-manager": "8.31.1", 1506 - "@typescript-eslint/type-utils": "8.31.1", 1507 - "@typescript-eslint/utils": "8.31.1", 1508 - "@typescript-eslint/visitor-keys": "8.31.1", 1509 "graphemer": "^1.4.0", 1510 - "ignore": "^5.3.1", 1511 "natural-compare": "^1.4.0", 1512 - "ts-api-utils": "^2.0.1" 1513 }, 1514 "engines": { 1515 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1519 "url": "https://opencollective.com/typescript-eslint" 1520 }, 1521 "peerDependencies": { 1522 - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", 1523 "eslint": "^8.57.0 || ^9.0.0", 1524 "typescript": ">=4.8.4 <5.9.0" 1525 } 1526 }, 1527 "node_modules/@typescript-eslint/parser": { 1528 - "version": "8.31.1", 1529 - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.1.tgz", 1530 - "integrity": "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==", 1531 "dev": true, 1532 "license": "MIT", 1533 "dependencies": { 1534 - "@typescript-eslint/scope-manager": "8.31.1", 1535 - "@typescript-eslint/types": "8.31.1", 1536 - "@typescript-eslint/typescript-estree": "8.31.1", 1537 - "@typescript-eslint/visitor-keys": "8.31.1", 1538 "debug": "^4.3.4" 1539 }, 1540 "engines": { ··· 1549 "typescript": ">=4.8.4 <5.9.0" 1550 } 1551 }, 1552 "node_modules/@typescript-eslint/scope-manager": { 1553 - "version": "8.31.1", 1554 - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz", 1555 - "integrity": "sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw==", 1556 "dev": true, 1557 "license": "MIT", 1558 "dependencies": { 1559 - "@typescript-eslint/types": "8.31.1", 1560 - "@typescript-eslint/visitor-keys": "8.31.1" 1561 }, 1562 "engines": { 1563 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1567 "url": "https://opencollective.com/typescript-eslint" 1568 } 1569 }, 1570 "node_modules/@typescript-eslint/type-utils": { 1571 - "version": "8.31.1", 1572 - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz", 1573 - "integrity": "sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA==", 1574 "dev": true, 1575 "license": "MIT", 1576 "dependencies": { 1577 - "@typescript-eslint/typescript-estree": "8.31.1", 1578 - "@typescript-eslint/utils": "8.31.1", 1579 "debug": "^4.3.4", 1580 - "ts-api-utils": "^2.0.1" 1581 }, 1582 "engines": { 1583 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1592 } 1593 }, 1594 "node_modules/@typescript-eslint/types": { 1595 - "version": "8.31.1", 1596 - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.1.tgz", 1597 - "integrity": "sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==", 1598 "dev": true, 1599 "license": "MIT", 1600 "engines": { ··· 1606 } 1607 }, 1608 "node_modules/@typescript-eslint/typescript-estree": { 1609 - "version": "8.31.1", 1610 - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz", 1611 - "integrity": "sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag==", 1612 "dev": true, 1613 "license": "MIT", 1614 "dependencies": { 1615 - "@typescript-eslint/types": "8.31.1", 1616 - "@typescript-eslint/visitor-keys": "8.31.1", 1617 "debug": "^4.3.4", 1618 "fast-glob": "^3.3.2", 1619 "is-glob": "^4.0.3", 1620 "minimatch": "^9.0.4", 1621 "semver": "^7.6.0", 1622 - "ts-api-utils": "^2.0.1" 1623 }, 1624 "engines": { 1625 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1658 "url": "https://github.com/sponsors/isaacs" 1659 } 1660 }, 1661 - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { 1662 - "version": "7.7.1", 1663 - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", 1664 - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", 1665 - "dev": true, 1666 - "license": "ISC", 1667 - "bin": { 1668 - "semver": "bin/semver.js" 1669 - }, 1670 - "engines": { 1671 - "node": ">=10" 1672 - } 1673 - }, 1674 "node_modules/@typescript-eslint/utils": { 1675 - "version": "8.31.1", 1676 - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.1.tgz", 1677 - "integrity": "sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==", 1678 "dev": true, 1679 "license": "MIT", 1680 "dependencies": { 1681 - "@eslint-community/eslint-utils": "^4.4.0", 1682 - "@typescript-eslint/scope-manager": "8.31.1", 1683 - "@typescript-eslint/types": "8.31.1", 1684 - "@typescript-eslint/typescript-estree": "8.31.1" 1685 }, 1686 "engines": { 1687 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1696 } 1697 }, 1698 "node_modules/@typescript-eslint/visitor-keys": { 1699 - "version": "8.31.1", 1700 - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz", 1701 - "integrity": "sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==", 1702 "dev": true, 1703 "license": "MIT", 1704 "dependencies": { 1705 - "@typescript-eslint/types": "8.31.1", 1706 "eslint-visitor-keys": "^4.2.0" 1707 }, 1708 "engines": { ··· 1713 "url": "https://opencollective.com/typescript-eslint" 1714 } 1715 }, 1716 "node_modules/@vitejs/plugin-react": { 1717 - "version": "4.4.1", 1718 - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", 1719 - "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==", 1720 "dev": true, 1721 "license": "MIT", 1722 "dependencies": { 1723 "@babel/core": "^7.26.10", 1724 "@babel/plugin-transform-react-jsx-self": "^7.25.9", 1725 "@babel/plugin-transform-react-jsx-source": "^7.25.9", 1726 "@types/babel__core": "^7.20.5", 1727 "react-refresh": "^0.17.0" 1728 }, ··· 1733 "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" 1734 } 1735 }, 1736 - "node_modules/accepts": { 1737 - "version": "2.0.0", 1738 - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", 1739 - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 1740 - "dev": true, 1741 - "license": "MIT", 1742 - "dependencies": { 1743 - "mime-types": "^3.0.0", 1744 - "negotiator": "^1.0.0" 1745 - }, 1746 - "engines": { 1747 - "node": ">= 0.6" 1748 - } 1749 - }, 1750 "node_modules/acorn": { 1751 "version": "8.14.1", 1752 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", ··· 1787 "url": "https://github.com/sponsors/epoberezkin" 1788 } 1789 }, 1790 "node_modules/ansi-styles": { 1791 "version": "4.3.0", 1792 "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", ··· 1823 "dev": true, 1824 "license": "MIT" 1825 }, 1826 - "node_modules/body-parser": { 1827 - "version": "2.2.0", 1828 - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", 1829 - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", 1830 - "dev": true, 1831 - "license": "MIT", 1832 - "dependencies": { 1833 - "bytes": "^3.1.2", 1834 - "content-type": "^1.0.5", 1835 - "debug": "^4.4.0", 1836 - "http-errors": "^2.0.0", 1837 - "iconv-lite": "^0.6.3", 1838 - "on-finished": "^2.4.1", 1839 - "qs": "^6.14.0", 1840 - "raw-body": "^3.0.0", 1841 - "type-is": "^2.0.0" 1842 - }, 1843 - "engines": { 1844 - "node": ">=18" 1845 - } 1846 - }, 1847 "node_modules/brace-expansion": { 1848 "version": "1.1.11", 1849 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", ··· 1901 "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 1902 } 1903 }, 1904 - "node_modules/bytes": { 1905 - "version": "3.1.2", 1906 - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 1907 - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 1908 "dev": true, 1909 - "license": "MIT", 1910 - "engines": { 1911 - "node": ">= 0.8" 1912 - } 1913 - }, 1914 - "node_modules/call-bind-apply-helpers": { 1915 - "version": "1.0.2", 1916 - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 1917 - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 1918 - "dev": true, 1919 - "license": "MIT", 1920 - "dependencies": { 1921 - "es-errors": "^1.3.0", 1922 - "function-bind": "^1.1.2" 1923 - }, 1924 - "engines": { 1925 - "node": ">= 0.4" 1926 - } 1927 - }, 1928 - "node_modules/call-bound": { 1929 - "version": "1.0.4", 1930 - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 1931 - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 1932 - "dev": true, 1933 - "license": "MIT", 1934 - "dependencies": { 1935 - "call-bind-apply-helpers": "^1.0.2", 1936 - "get-intrinsic": "^1.3.0" 1937 - }, 1938 - "engines": { 1939 - "node": ">= 0.4" 1940 - }, 1941 - "funding": { 1942 - "url": "https://github.com/sponsors/ljharb" 1943 - } 1944 }, 1945 "node_modules/callsites": { 1946 "version": "3.1.0", ··· 1953 } 1954 }, 1955 "node_modules/caniuse-lite": { 1956 - "version": "1.0.30001716", 1957 - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz", 1958 - "integrity": "sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw==", 1959 "dev": true, 1960 "funding": [ 1961 { ··· 1990 "url": "https://github.com/chalk/chalk?sponsor=1" 1991 } 1992 }, 1993 "node_modules/color-convert": { 1994 "version": "2.0.1", 1995 "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", ··· 2010 "dev": true, 2011 "license": "MIT" 2012 }, 2013 "node_modules/concat-map": { 2014 "version": "0.0.1", 2015 "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", ··· 2017 "dev": true, 2018 "license": "MIT" 2019 }, 2020 - "node_modules/content-disposition": { 2021 - "version": "1.0.0", 2022 - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", 2023 - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", 2024 - "dev": true, 2025 - "license": "MIT", 2026 - "dependencies": { 2027 - "safe-buffer": "5.2.1" 2028 - }, 2029 - "engines": { 2030 - "node": ">= 0.6" 2031 - } 2032 - }, 2033 - "node_modules/content-type": { 2034 - "version": "1.0.5", 2035 - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 2036 - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 2037 - "dev": true, 2038 - "license": "MIT", 2039 - "engines": { 2040 - "node": ">= 0.6" 2041 - } 2042 - }, 2043 "node_modules/convert-source-map": { 2044 "version": "2.0.0", 2045 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", ··· 2048 "license": "MIT" 2049 }, 2050 "node_modules/cookie": { 2051 - "version": "0.7.2", 2052 - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", 2053 - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", 2054 - "dev": true, 2055 - "license": "MIT", 2056 - "engines": { 2057 - "node": ">= 0.6" 2058 - } 2059 - }, 2060 - "node_modules/cookie-signature": { 2061 - "version": "1.2.2", 2062 - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", 2063 - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", 2064 - "dev": true, 2065 "license": "MIT", 2066 "engines": { 2067 - "node": ">=6.6.0" 2068 - } 2069 - }, 2070 - "node_modules/cors": { 2071 - "version": "2.8.5", 2072 - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 2073 - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 2074 - "dev": true, 2075 - "license": "MIT", 2076 - "dependencies": { 2077 - "object-assign": "^4", 2078 - "vary": "^1" 2079 - }, 2080 - "engines": { 2081 - "node": ">= 0.10" 2082 } 2083 }, 2084 "node_modules/cross-spawn": { ··· 2104 "license": "MIT" 2105 }, 2106 "node_modules/debug": { 2107 - "version": "4.4.0", 2108 - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 2109 - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 2110 "dev": true, 2111 "license": "MIT", 2112 "dependencies": { ··· 2128 "dev": true, 2129 "license": "MIT" 2130 }, 2131 - "node_modules/depd": { 2132 "version": "2.0.0", 2133 - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 2134 - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 2135 "dev": true, 2136 "license": "MIT", 2137 "engines": { 2138 - "node": ">= 0.8" 2139 - } 2140 - }, 2141 - "node_modules/dunder-proto": { 2142 - "version": "1.0.1", 2143 - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 2144 - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 2145 - "dev": true, 2146 - "license": "MIT", 2147 - "dependencies": { 2148 - "call-bind-apply-helpers": "^1.0.1", 2149 - "es-errors": "^1.3.0", 2150 - "gopd": "^1.2.0" 2151 - }, 2152 - "engines": { 2153 - "node": ">= 0.4" 2154 } 2155 }, 2156 - "node_modules/ee-first": { 2157 - "version": "1.1.1", 2158 - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 2159 - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 2160 - "dev": true, 2161 - "license": "MIT" 2162 - }, 2163 "node_modules/electron-to-chromium": { 2164 - "version": "1.5.149", 2165 - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.149.tgz", 2166 - "integrity": "sha512-UyiO82eb9dVOx8YO3ajDf9jz2kKyt98DEITRdeLPstOEuTlLzDA4Gyq5K9he71TQziU5jUVu2OAu5N48HmQiyQ==", 2167 "dev": true, 2168 "license": "ISC" 2169 }, 2170 - "node_modules/encodeurl": { 2171 - "version": "2.0.0", 2172 - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 2173 - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 2174 "dev": true, 2175 - "license": "MIT", 2176 - "engines": { 2177 - "node": ">= 0.8" 2178 - } 2179 - }, 2180 - "node_modules/es-define-property": { 2181 - "version": "1.0.1", 2182 - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 2183 - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 2184 - "dev": true, 2185 - "license": "MIT", 2186 - "engines": { 2187 - "node": ">= 0.4" 2188 - } 2189 - }, 2190 - "node_modules/es-errors": { 2191 - "version": "1.3.0", 2192 - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 2193 - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 2194 - "dev": true, 2195 - "license": "MIT", 2196 - "engines": { 2197 - "node": ">= 0.4" 2198 - } 2199 - }, 2200 - "node_modules/es-object-atoms": { 2201 - "version": "1.1.1", 2202 - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 2203 - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 2204 - "dev": true, 2205 - "license": "MIT", 2206 - "dependencies": { 2207 - "es-errors": "^1.3.0" 2208 - }, 2209 - "engines": { 2210 - "node": ">= 0.4" 2211 - } 2212 }, 2213 "node_modules/esbuild": { 2214 - "version": "0.25.3", 2215 - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", 2216 - "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", 2217 "dev": true, 2218 "hasInstallScript": true, 2219 "license": "MIT", ··· 2224 "node": ">=18" 2225 }, 2226 "optionalDependencies": { 2227 - "@esbuild/aix-ppc64": "0.25.3", 2228 - "@esbuild/android-arm": "0.25.3", 2229 - "@esbuild/android-arm64": "0.25.3", 2230 - "@esbuild/android-x64": "0.25.3", 2231 - "@esbuild/darwin-arm64": "0.25.3", 2232 - "@esbuild/darwin-x64": "0.25.3", 2233 - "@esbuild/freebsd-arm64": "0.25.3", 2234 - "@esbuild/freebsd-x64": "0.25.3", 2235 - "@esbuild/linux-arm": "0.25.3", 2236 - "@esbuild/linux-arm64": "0.25.3", 2237 - "@esbuild/linux-ia32": "0.25.3", 2238 - "@esbuild/linux-loong64": "0.25.3", 2239 - "@esbuild/linux-mips64el": "0.25.3", 2240 - "@esbuild/linux-ppc64": "0.25.3", 2241 - "@esbuild/linux-riscv64": "0.25.3", 2242 - "@esbuild/linux-s390x": "0.25.3", 2243 - "@esbuild/linux-x64": "0.25.3", 2244 - "@esbuild/netbsd-arm64": "0.25.3", 2245 - "@esbuild/netbsd-x64": "0.25.3", 2246 - "@esbuild/openbsd-arm64": "0.25.3", 2247 - "@esbuild/openbsd-x64": "0.25.3", 2248 - "@esbuild/sunos-x64": "0.25.3", 2249 - "@esbuild/win32-arm64": "0.25.3", 2250 - "@esbuild/win32-ia32": "0.25.3", 2251 - "@esbuild/win32-x64": "0.25.3" 2252 } 2253 }, 2254 "node_modules/escalade": { ··· 2261 "node": ">=6" 2262 } 2263 }, 2264 - "node_modules/escape-html": { 2265 - "version": "1.0.3", 2266 - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 2267 - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 2268 - "dev": true, 2269 - "license": "MIT" 2270 - }, 2271 "node_modules/escape-string-regexp": { 2272 "version": "4.0.0", 2273 "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", ··· 2282 } 2283 }, 2284 "node_modules/eslint": { 2285 - "version": "9.26.0", 2286 - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz", 2287 - "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==", 2288 "dev": true, 2289 "license": "MIT", 2290 "dependencies": { ··· 2292 "@eslint-community/regexpp": "^4.12.1", 2293 "@eslint/config-array": "^0.20.0", 2294 "@eslint/config-helpers": "^0.2.1", 2295 - "@eslint/core": "^0.13.0", 2296 "@eslint/eslintrc": "^3.3.1", 2297 - "@eslint/js": "9.26.0", 2298 - "@eslint/plugin-kit": "^0.2.8", 2299 "@humanfs/node": "^0.16.6", 2300 "@humanwhocodes/module-importer": "^1.0.1", 2301 "@humanwhocodes/retry": "^0.4.2", 2302 - "@modelcontextprotocol/sdk": "^1.8.0", 2303 "@types/estree": "^1.0.6", 2304 "@types/json-schema": "^7.0.15", 2305 "ajv": "^6.12.4", ··· 2323 "lodash.merge": "^4.6.2", 2324 "minimatch": "^3.1.2", 2325 "natural-compare": "^1.4.0", 2326 - "optionator": "^0.9.3", 2327 - "zod": "^3.24.2" 2328 }, 2329 "bin": { 2330 "eslint": "bin/eslint.js" ··· 2385 } 2386 }, 2387 "node_modules/eslint-visitor-keys": { 2388 "version": "4.2.0", 2389 "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", 2390 "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", ··· 2415 "url": "https://opencollective.com/eslint" 2416 } 2417 }, 2418 "node_modules/esquery": { 2419 "version": "1.6.0", 2420 "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", ··· 2461 "node": ">=0.10.0" 2462 } 2463 }, 2464 - "node_modules/etag": { 2465 - "version": "1.8.1", 2466 - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 2467 - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 2468 - "dev": true, 2469 - "license": "MIT", 2470 - "engines": { 2471 - "node": ">= 0.6" 2472 - } 2473 - }, 2474 - "node_modules/eventsource": { 2475 - "version": "3.0.6", 2476 - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", 2477 - "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==", 2478 - "dev": true, 2479 - "license": "MIT", 2480 - "dependencies": { 2481 - "eventsource-parser": "^3.0.1" 2482 - }, 2483 - "engines": { 2484 - "node": ">=18.0.0" 2485 - } 2486 - }, 2487 - "node_modules/eventsource-parser": { 2488 - "version": "3.0.1", 2489 - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", 2490 - "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", 2491 - "dev": true, 2492 - "license": "MIT", 2493 - "engines": { 2494 - "node": ">=18.0.0" 2495 - } 2496 - }, 2497 - "node_modules/express": { 2498 - "version": "5.1.0", 2499 - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", 2500 - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", 2501 - "dev": true, 2502 - "license": "MIT", 2503 - "dependencies": { 2504 - "accepts": "^2.0.0", 2505 - "body-parser": "^2.2.0", 2506 - "content-disposition": "^1.0.0", 2507 - "content-type": "^1.0.5", 2508 - "cookie": "^0.7.1", 2509 - "cookie-signature": "^1.2.1", 2510 - "debug": "^4.4.0", 2511 - "encodeurl": "^2.0.0", 2512 - "escape-html": "^1.0.3", 2513 - "etag": "^1.8.1", 2514 - "finalhandler": "^2.1.0", 2515 - "fresh": "^2.0.0", 2516 - "http-errors": "^2.0.0", 2517 - "merge-descriptors": "^2.0.0", 2518 - "mime-types": "^3.0.0", 2519 - "on-finished": "^2.4.1", 2520 - "once": "^1.4.0", 2521 - "parseurl": "^1.3.3", 2522 - "proxy-addr": "^2.0.7", 2523 - "qs": "^6.14.0", 2524 - "range-parser": "^1.2.1", 2525 - "router": "^2.2.0", 2526 - "send": "^1.1.0", 2527 - "serve-static": "^2.2.0", 2528 - "statuses": "^2.0.1", 2529 - "type-is": "^2.0.1", 2530 - "vary": "^1.1.2" 2531 - }, 2532 - "engines": { 2533 - "node": ">= 18" 2534 - }, 2535 - "funding": { 2536 - "type": "opencollective", 2537 - "url": "https://opencollective.com/express" 2538 - } 2539 - }, 2540 - "node_modules/express-rate-limit": { 2541 - "version": "7.5.0", 2542 - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", 2543 - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", 2544 - "dev": true, 2545 - "license": "MIT", 2546 - "engines": { 2547 - "node": ">= 16" 2548 - }, 2549 - "funding": { 2550 - "url": "https://github.com/sponsors/express-rate-limit" 2551 - }, 2552 - "peerDependencies": { 2553 - "express": "^4.11 || 5 || ^5.0.0-beta.1" 2554 - } 2555 - }, 2556 "node_modules/fast-deep-equal": { 2557 "version": "3.1.3", 2558 "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", ··· 2640 "node": ">=8" 2641 } 2642 }, 2643 - "node_modules/finalhandler": { 2644 - "version": "2.1.0", 2645 - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", 2646 - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", 2647 - "dev": true, 2648 - "license": "MIT", 2649 - "dependencies": { 2650 - "debug": "^4.4.0", 2651 - "encodeurl": "^2.0.0", 2652 - "escape-html": "^1.0.3", 2653 - "on-finished": "^2.4.1", 2654 - "parseurl": "^1.3.3", 2655 - "statuses": "^2.0.1" 2656 - }, 2657 - "engines": { 2658 - "node": ">= 0.8" 2659 - } 2660 - }, 2661 "node_modules/find-up": { 2662 "version": "5.0.0", 2663 "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", ··· 2696 "dev": true, 2697 "license": "ISC" 2698 }, 2699 - "node_modules/forwarded": { 2700 - "version": "0.2.0", 2701 - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 2702 - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 2703 - "dev": true, 2704 - "license": "MIT", 2705 - "engines": { 2706 - "node": ">= 0.6" 2707 - } 2708 - }, 2709 - "node_modules/fresh": { 2710 - "version": "2.0.0", 2711 - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", 2712 - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", 2713 - "dev": true, 2714 - "license": "MIT", 2715 - "engines": { 2716 - "node": ">= 0.8" 2717 - } 2718 - }, 2719 "node_modules/fsevents": { 2720 "version": "2.3.3", 2721 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", ··· 2731 "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 2732 } 2733 }, 2734 - "node_modules/function-bind": { 2735 - "version": "1.1.2", 2736 - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 2737 - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 2738 - "dev": true, 2739 - "license": "MIT", 2740 - "funding": { 2741 - "url": "https://github.com/sponsors/ljharb" 2742 - } 2743 - }, 2744 "node_modules/gensync": { 2745 "version": "1.0.0-beta.2", 2746 "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", ··· 2751 "node": ">=6.9.0" 2752 } 2753 }, 2754 - "node_modules/get-intrinsic": { 2755 - "version": "1.3.0", 2756 - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 2757 - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 2758 - "dev": true, 2759 - "license": "MIT", 2760 - "dependencies": { 2761 - "call-bind-apply-helpers": "^1.0.2", 2762 - "es-define-property": "^1.0.1", 2763 - "es-errors": "^1.3.0", 2764 - "es-object-atoms": "^1.1.1", 2765 - "function-bind": "^1.1.2", 2766 - "get-proto": "^1.0.1", 2767 - "gopd": "^1.2.0", 2768 - "has-symbols": "^1.1.0", 2769 - "hasown": "^2.0.2", 2770 - "math-intrinsics": "^1.1.0" 2771 - }, 2772 - "engines": { 2773 - "node": ">= 0.4" 2774 - }, 2775 - "funding": { 2776 - "url": "https://github.com/sponsors/ljharb" 2777 - } 2778 - }, 2779 - "node_modules/get-proto": { 2780 - "version": "1.0.1", 2781 - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 2782 - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 2783 "dev": true, 2784 - "license": "MIT", 2785 - "dependencies": { 2786 - "dunder-proto": "^1.0.1", 2787 - "es-object-atoms": "^1.0.0" 2788 - }, 2789 "engines": { 2790 - "node": ">= 0.4" 2791 } 2792 }, 2793 "node_modules/glob-parent": { ··· 2804 } 2805 }, 2806 "node_modules/globals": { 2807 - "version": "16.0.0", 2808 - "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", 2809 - "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", 2810 "dev": true, 2811 "license": "MIT", 2812 "engines": { ··· 2816 "url": "https://github.com/sponsors/sindresorhus" 2817 } 2818 }, 2819 - "node_modules/gopd": { 2820 - "version": "1.2.0", 2821 - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 2822 - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 2823 - "dev": true, 2824 - "license": "MIT", 2825 - "engines": { 2826 - "node": ">= 0.4" 2827 - }, 2828 - "funding": { 2829 - "url": "https://github.com/sponsors/ljharb" 2830 - } 2831 - }, 2832 "node_modules/graphemer": { 2833 "version": "1.4.0", 2834 "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", ··· 2845 "node": ">=8" 2846 } 2847 }, 2848 - "node_modules/has-symbols": { 2849 - "version": "1.1.0", 2850 - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 2851 - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 2852 - "dev": true, 2853 - "license": "MIT", 2854 - "engines": { 2855 - "node": ">= 0.4" 2856 - }, 2857 - "funding": { 2858 - "url": "https://github.com/sponsors/ljharb" 2859 - } 2860 - }, 2861 - "node_modules/hasown": { 2862 - "version": "2.0.2", 2863 - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 2864 - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 2865 - "dev": true, 2866 - "license": "MIT", 2867 - "dependencies": { 2868 - "function-bind": "^1.1.2" 2869 - }, 2870 - "engines": { 2871 - "node": ">= 0.4" 2872 - } 2873 - }, 2874 - "node_modules/http-errors": { 2875 - "version": "2.0.0", 2876 - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 2877 - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 2878 - "dev": true, 2879 - "license": "MIT", 2880 - "dependencies": { 2881 - "depd": "2.0.0", 2882 - "inherits": "2.0.4", 2883 - "setprototypeof": "1.2.0", 2884 - "statuses": "2.0.1", 2885 - "toidentifier": "1.0.1" 2886 - }, 2887 - "engines": { 2888 - "node": ">= 0.8" 2889 - } 2890 - }, 2891 - "node_modules/iconv-lite": { 2892 - "version": "0.6.3", 2893 - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 2894 - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 2895 - "dev": true, 2896 - "license": "MIT", 2897 - "dependencies": { 2898 - "safer-buffer": ">= 2.1.2 < 3.0.0" 2899 - }, 2900 - "engines": { 2901 - "node": ">=0.10.0" 2902 - } 2903 - }, 2904 "node_modules/ignore": { 2905 "version": "5.3.2", 2906 "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", ··· 2938 "node": ">=0.8.19" 2939 } 2940 }, 2941 - "node_modules/inherits": { 2942 - "version": "2.0.4", 2943 - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 2944 - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 2945 - "dev": true, 2946 - "license": "ISC" 2947 - }, 2948 - "node_modules/ipaddr.js": { 2949 - "version": "1.9.1", 2950 - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 2951 - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 2952 "dev": true, 2953 "license": "MIT", 2954 "engines": { 2955 - "node": ">= 0.10" 2956 } 2957 }, 2958 "node_modules/is-extglob": { ··· 2965 "node": ">=0.10.0" 2966 } 2967 }, 2968 "node_modules/is-glob": { 2969 "version": "4.0.3", 2970 "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", ··· 2988 "node": ">=0.12.0" 2989 } 2990 }, 2991 - "node_modules/is-promise": { 2992 - "version": "4.0.0", 2993 - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 2994 - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", 2995 "dev": true, 2996 - "license": "MIT" 2997 }, 2998 "node_modules/isexe": { 2999 "version": "2.0.0", ··· 3007 "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz", 3008 "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==", 3009 "license": "MIT" 3010 }, 3011 "node_modules/js-tokens": { 3012 "version": "4.0.0", ··· 3132 "yallist": "^3.0.2" 3133 } 3134 }, 3135 - "node_modules/math-intrinsics": { 3136 - "version": "1.1.0", 3137 - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 3138 - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 3139 - "dev": true, 3140 - "license": "MIT", 3141 - "engines": { 3142 - "node": ">= 0.4" 3143 - } 3144 - }, 3145 - "node_modules/media-typer": { 3146 - "version": "1.1.0", 3147 - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", 3148 - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", 3149 - "dev": true, 3150 - "license": "MIT", 3151 - "engines": { 3152 - "node": ">= 0.8" 3153 - } 3154 - }, 3155 - "node_modules/merge-descriptors": { 3156 - "version": "2.0.0", 3157 - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", 3158 - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", 3159 - "dev": true, 3160 - "license": "MIT", 3161 - "engines": { 3162 - "node": ">=18" 3163 - }, 3164 - "funding": { 3165 - "url": "https://github.com/sponsors/sindresorhus" 3166 - } 3167 - }, 3168 "node_modules/merge2": { 3169 "version": "1.4.1", 3170 "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", ··· 3189 "node": ">=8.6" 3190 } 3191 }, 3192 - "node_modules/mime-db": { 3193 - "version": "1.54.0", 3194 - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 3195 - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 3196 - "dev": true, 3197 - "license": "MIT", 3198 - "engines": { 3199 - "node": ">= 0.6" 3200 - } 3201 - }, 3202 - "node_modules/mime-types": { 3203 - "version": "3.0.1", 3204 - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", 3205 - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", 3206 - "dev": true, 3207 - "license": "MIT", 3208 - "dependencies": { 3209 - "mime-db": "^1.54.0" 3210 - }, 3211 - "engines": { 3212 - "node": ">= 0.6" 3213 - } 3214 - }, 3215 "node_modules/minimatch": { 3216 "version": "3.1.2", 3217 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", ··· 3233 "license": "MIT" 3234 }, 3235 "node_modules/multiformats": { 3236 - "version": "9.9.0", 3237 - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", 3238 - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 3239 - "license": "(Apache-2.0 AND MIT)" 3240 }, 3241 "node_modules/nanoid": { 3242 "version": "3.3.11", ··· 3264 "dev": true, 3265 "license": "MIT" 3266 }, 3267 - "node_modules/negotiator": { 3268 - "version": "1.0.0", 3269 - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 3270 - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 3271 - "dev": true, 3272 - "license": "MIT", 3273 - "engines": { 3274 - "node": ">= 0.6" 3275 - } 3276 - }, 3277 "node_modules/node-releases": { 3278 "version": "2.0.19", 3279 "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", ··· 3281 "dev": true, 3282 "license": "MIT" 3283 }, 3284 - "node_modules/object-assign": { 3285 - "version": "4.1.1", 3286 - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 3287 - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 3288 - "dev": true, 3289 - "license": "MIT", 3290 - "engines": { 3291 - "node": ">=0.10.0" 3292 - } 3293 - }, 3294 - "node_modules/object-inspect": { 3295 - "version": "1.13.4", 3296 - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 3297 - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 3298 "dev": true, 3299 "license": "MIT", 3300 "engines": { 3301 - "node": ">= 0.4" 3302 }, 3303 "funding": { 3304 - "url": "https://github.com/sponsors/ljharb" 3305 - } 3306 - }, 3307 - "node_modules/on-finished": { 3308 - "version": "2.4.1", 3309 - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 3310 - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 3311 - "dev": true, 3312 - "license": "MIT", 3313 - "dependencies": { 3314 - "ee-first": "1.1.1" 3315 - }, 3316 - "engines": { 3317 - "node": ">= 0.8" 3318 - } 3319 - }, 3320 - "node_modules/once": { 3321 - "version": "1.4.0", 3322 - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 3323 - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 3324 - "dev": true, 3325 - "license": "ISC", 3326 - "dependencies": { 3327 - "wrappy": "1" 3328 } 3329 }, 3330 "node_modules/optionator": { ··· 3390 "node": ">=6" 3391 } 3392 }, 3393 - "node_modules/parseurl": { 3394 - "version": "1.3.3", 3395 - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 3396 - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 3397 - "dev": true, 3398 - "license": "MIT", 3399 - "engines": { 3400 - "node": ">= 0.8" 3401 - } 3402 - }, 3403 "node_modules/path-exists": { 3404 "version": "4.0.0", 3405 "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", ··· 3420 "node": ">=8" 3421 } 3422 }, 3423 - "node_modules/path-to-regexp": { 3424 - "version": "8.2.0", 3425 - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", 3426 - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", 3427 - "dev": true, 3428 - "license": "MIT", 3429 - "engines": { 3430 - "node": ">=16" 3431 - } 3432 - }, 3433 "node_modules/picocolors": { 3434 "version": "1.1.1", 3435 "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", ··· 3448 }, 3449 "funding": { 3450 "url": "https://github.com/sponsors/jonschlinkert" 3451 - } 3452 - }, 3453 - "node_modules/pkce-challenge": { 3454 - "version": "5.0.0", 3455 - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", 3456 - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", 3457 - "dev": true, 3458 - "license": "MIT", 3459 - "engines": { 3460 - "node": ">=16.20.0" 3461 } 3462 }, 3463 "node_modules/postcss": { ··· 3499 "node": ">= 0.8.0" 3500 } 3501 }, 3502 - "node_modules/proxy-addr": { 3503 - "version": "2.0.7", 3504 - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 3505 - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 3506 - "dev": true, 3507 - "license": "MIT", 3508 - "dependencies": { 3509 - "forwarded": "0.2.0", 3510 - "ipaddr.js": "1.9.1" 3511 - }, 3512 - "engines": { 3513 - "node": ">= 0.10" 3514 - } 3515 - }, 3516 "node_modules/punycode": { 3517 "version": "2.3.1", 3518 "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", ··· 3523 "node": ">=6" 3524 } 3525 }, 3526 - "node_modules/qs": { 3527 - "version": "6.14.0", 3528 - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", 3529 - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", 3530 - "dev": true, 3531 - "license": "BSD-3-Clause", 3532 - "dependencies": { 3533 - "side-channel": "^1.1.0" 3534 - }, 3535 - "engines": { 3536 - "node": ">=0.6" 3537 - }, 3538 - "funding": { 3539 - "url": "https://github.com/sponsors/ljharb" 3540 - } 3541 - }, 3542 "node_modules/queue-microtask": { 3543 "version": "1.2.3", 3544 "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", ··· 3560 ], 3561 "license": "MIT" 3562 }, 3563 - "node_modules/range-parser": { 3564 - "version": "1.2.1", 3565 - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 3566 - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 3567 - "dev": true, 3568 - "license": "MIT", 3569 - "engines": { 3570 - "node": ">= 0.6" 3571 - } 3572 - }, 3573 - "node_modules/raw-body": { 3574 - "version": "3.0.0", 3575 - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 3576 - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 3577 - "dev": true, 3578 - "license": "MIT", 3579 - "dependencies": { 3580 - "bytes": "3.1.2", 3581 - "http-errors": "2.0.0", 3582 - "iconv-lite": "0.6.3", 3583 - "unpipe": "1.0.0" 3584 - }, 3585 - "engines": { 3586 - "node": ">= 0.8" 3587 - } 3588 - }, 3589 "node_modules/react": { 3590 "version": "19.1.0", 3591 "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", ··· 3618 } 3619 }, 3620 "node_modules/react-router": { 3621 - "version": "7.5.3", 3622 - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz", 3623 - "integrity": "sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==", 3624 "license": "MIT", 3625 "dependencies": { 3626 "cookie": "^1.0.1", 3627 - "set-cookie-parser": "^2.6.0", 3628 - "turbo-stream": "2.4.0" 3629 }, 3630 "engines": { 3631 "node": ">=20.0.0" ··· 3641 } 3642 }, 3643 "node_modules/react-router-dom": { 3644 - "version": "7.5.3", 3645 - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.3.tgz", 3646 - "integrity": "sha512-cK0jSaTyW4jV9SRKAItMIQfWZ/D6WEZafgHuuCb9g+SjhLolY78qc+De4w/Cz9ybjvLzShAmaIMEXt8iF1Cm+A==", 3647 "license": "MIT", 3648 "dependencies": { 3649 - "react-router": "7.5.3" 3650 }, 3651 "engines": { 3652 "node": ">=20.0.0" ··· 3656 "react-dom": ">=18" 3657 } 3658 }, 3659 - "node_modules/react-router/node_modules/cookie": { 3660 - "version": "1.0.2", 3661 - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", 3662 - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", 3663 "license": "MIT", 3664 "engines": { 3665 - "node": ">=18" 3666 } 3667 }, 3668 "node_modules/resolve-from": { ··· 3687 } 3688 }, 3689 "node_modules/rollup": { 3690 - "version": "4.40.1", 3691 - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", 3692 - "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==", 3693 "dev": true, 3694 "license": "MIT", 3695 "dependencies": { ··· 3703 "npm": ">=8.0.0" 3704 }, 3705 "optionalDependencies": { 3706 - "@rollup/rollup-android-arm-eabi": "4.40.1", 3707 - "@rollup/rollup-android-arm64": "4.40.1", 3708 - "@rollup/rollup-darwin-arm64": "4.40.1", 3709 - "@rollup/rollup-darwin-x64": "4.40.1", 3710 - "@rollup/rollup-freebsd-arm64": "4.40.1", 3711 - "@rollup/rollup-freebsd-x64": "4.40.1", 3712 - "@rollup/rollup-linux-arm-gnueabihf": "4.40.1", 3713 - "@rollup/rollup-linux-arm-musleabihf": "4.40.1", 3714 - "@rollup/rollup-linux-arm64-gnu": "4.40.1", 3715 - "@rollup/rollup-linux-arm64-musl": "4.40.1", 3716 - "@rollup/rollup-linux-loongarch64-gnu": "4.40.1", 3717 - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1", 3718 - "@rollup/rollup-linux-riscv64-gnu": "4.40.1", 3719 - "@rollup/rollup-linux-riscv64-musl": "4.40.1", 3720 - "@rollup/rollup-linux-s390x-gnu": "4.40.1", 3721 - "@rollup/rollup-linux-x64-gnu": "4.40.1", 3722 - "@rollup/rollup-linux-x64-musl": "4.40.1", 3723 - "@rollup/rollup-win32-arm64-msvc": "4.40.1", 3724 - "@rollup/rollup-win32-ia32-msvc": "4.40.1", 3725 - "@rollup/rollup-win32-x64-msvc": "4.40.1", 3726 "fsevents": "~2.3.2" 3727 } 3728 }, 3729 - "node_modules/router": { 3730 - "version": "2.2.0", 3731 - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", 3732 - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", 3733 "dev": true, 3734 "license": "MIT", 3735 "dependencies": { 3736 - "debug": "^4.4.0", 3737 - "depd": "^2.0.0", 3738 - "is-promise": "^4.0.0", 3739 - "parseurl": "^1.3.3", 3740 - "path-to-regexp": "^8.0.0" 3741 }, 3742 "engines": { 3743 - "node": ">= 18" 3744 } 3745 }, 3746 "node_modules/run-parallel": { ··· 3767 "queue-microtask": "^1.2.2" 3768 } 3769 }, 3770 - "node_modules/safe-buffer": { 3771 - "version": "5.2.1", 3772 - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 3773 - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 3774 - "dev": true, 3775 - "funding": [ 3776 - { 3777 - "type": "github", 3778 - "url": "https://github.com/sponsors/feross" 3779 - }, 3780 - { 3781 - "type": "patreon", 3782 - "url": "https://www.patreon.com/feross" 3783 - }, 3784 - { 3785 - "type": "consulting", 3786 - "url": "https://feross.org/support" 3787 - } 3788 - ], 3789 - "license": "MIT" 3790 - }, 3791 - "node_modules/safer-buffer": { 3792 - "version": "2.1.2", 3793 - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 3794 - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 3795 - "dev": true, 3796 - "license": "MIT" 3797 - }, 3798 "node_modules/scheduler": { 3799 "version": "0.26.0", 3800 "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", ··· 3802 "license": "MIT" 3803 }, 3804 "node_modules/semver": { 3805 - "version": "6.3.1", 3806 - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 3807 - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 3808 "dev": true, 3809 "license": "ISC", 3810 "bin": { 3811 "semver": "bin/semver.js" 3812 - } 3813 - }, 3814 - "node_modules/send": { 3815 - "version": "1.2.0", 3816 - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", 3817 - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", 3818 - "dev": true, 3819 - "license": "MIT", 3820 - "dependencies": { 3821 - "debug": "^4.3.5", 3822 - "encodeurl": "^2.0.0", 3823 - "escape-html": "^1.0.3", 3824 - "etag": "^1.8.1", 3825 - "fresh": "^2.0.0", 3826 - "http-errors": "^2.0.0", 3827 - "mime-types": "^3.0.1", 3828 - "ms": "^2.1.3", 3829 - "on-finished": "^2.4.1", 3830 - "range-parser": "^1.2.1", 3831 - "statuses": "^2.0.1" 3832 }, 3833 "engines": { 3834 - "node": ">= 18" 3835 - } 3836 - }, 3837 - "node_modules/serve-static": { 3838 - "version": "2.2.0", 3839 - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", 3840 - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", 3841 - "dev": true, 3842 - "license": "MIT", 3843 - "dependencies": { 3844 - "encodeurl": "^2.0.0", 3845 - "escape-html": "^1.0.3", 3846 - "parseurl": "^1.3.3", 3847 - "send": "^1.2.0" 3848 - }, 3849 - "engines": { 3850 - "node": ">= 18" 3851 } 3852 }, 3853 "node_modules/set-cookie-parser": { ··· 3855 "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", 3856 "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", 3857 "license": "MIT" 3858 - }, 3859 - "node_modules/setprototypeof": { 3860 - "version": "1.2.0", 3861 - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 3862 - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 3863 - "dev": true, 3864 - "license": "ISC" 3865 }, 3866 "node_modules/shebang-command": { 3867 "version": "2.0.0", ··· 3886 "node": ">=8" 3887 } 3888 }, 3889 - "node_modules/side-channel": { 3890 - "version": "1.1.0", 3891 - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 3892 - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 3893 "dev": true, 3894 - "license": "MIT", 3895 - "dependencies": { 3896 - "es-errors": "^1.3.0", 3897 - "object-inspect": "^1.13.3", 3898 - "side-channel-list": "^1.0.0", 3899 - "side-channel-map": "^1.0.1", 3900 - "side-channel-weakmap": "^1.0.2" 3901 - }, 3902 "engines": { 3903 - "node": ">= 0.4" 3904 - }, 3905 - "funding": { 3906 - "url": "https://github.com/sponsors/ljharb" 3907 } 3908 }, 3909 - "node_modules/side-channel-list": { 3910 - "version": "1.0.0", 3911 - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 3912 - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 3913 "dev": true, 3914 - "license": "MIT", 3915 - "dependencies": { 3916 - "es-errors": "^1.3.0", 3917 - "object-inspect": "^1.13.3" 3918 - }, 3919 "engines": { 3920 - "node": ">= 0.4" 3921 - }, 3922 - "funding": { 3923 - "url": "https://github.com/sponsors/ljharb" 3924 } 3925 }, 3926 - "node_modules/side-channel-map": { 3927 - "version": "1.0.1", 3928 - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 3929 - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 3930 "dev": true, 3931 "license": "MIT", 3932 "dependencies": { 3933 - "call-bound": "^1.0.2", 3934 - "es-errors": "^1.3.0", 3935 - "get-intrinsic": "^1.2.5", 3936 - "object-inspect": "^1.13.3" 3937 - }, 3938 "engines": { 3939 - "node": ">= 0.4" 3940 - }, 3941 - "funding": { 3942 - "url": "https://github.com/sponsors/ljharb" 3943 } 3944 }, 3945 - "node_modules/side-channel-weakmap": { 3946 - "version": "1.0.2", 3947 - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 3948 - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 3949 "dev": true, 3950 "license": "MIT", 3951 "dependencies": { 3952 - "call-bound": "^1.0.2", 3953 - "es-errors": "^1.3.0", 3954 - "get-intrinsic": "^1.2.5", 3955 - "object-inspect": "^1.13.3", 3956 - "side-channel-map": "^1.0.1" 3957 }, 3958 "engines": { 3959 - "node": ">= 0.4" 3960 - }, 3961 - "funding": { 3962 - "url": "https://github.com/sponsors/ljharb" 3963 - } 3964 - }, 3965 - "node_modules/source-map-js": { 3966 - "version": "1.2.1", 3967 - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 3968 - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 3969 - "dev": true, 3970 - "license": "BSD-3-Clause", 3971 - "engines": { 3972 - "node": ">=0.10.0" 3973 } 3974 }, 3975 - "node_modules/statuses": { 3976 - "version": "2.0.1", 3977 - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 3978 - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 3979 "dev": true, 3980 "license": "MIT", 3981 "engines": { 3982 - "node": ">= 0.8" 3983 } 3984 }, 3985 "node_modules/strip-json-comments": { ··· 4008 "node": ">=8" 4009 } 4010 }, 4011 "node_modules/tinyglobby": { 4012 - "version": "0.2.13", 4013 - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", 4014 - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", 4015 "dev": true, 4016 "license": "MIT", 4017 "dependencies": { ··· 4026 } 4027 }, 4028 "node_modules/tinyglobby/node_modules/fdir": { 4029 - "version": "6.4.4", 4030 - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", 4031 - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", 4032 "dev": true, 4033 "license": "MIT", 4034 "peerDependencies": { ··· 4054 } 4055 }, 4056 "node_modules/tlds": { 4057 - "version": "1.258.0", 4058 - "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.258.0.tgz", 4059 - "integrity": "sha512-XGhStWuOlBA5D8QnyN2xtgB2cUOdJ3ztisne1DYVWMcVH29qh8eQIpRmP3HnuJLdgyzG0HpdGzRMu1lm/Oictw==", 4060 "license": "MIT", 4061 "bin": { 4062 "tlds": "bin.js" ··· 4073 }, 4074 "engines": { 4075 "node": ">=8.0" 4076 - } 4077 - }, 4078 - "node_modules/toidentifier": { 4079 - "version": "1.0.1", 4080 - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 4081 - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 4082 - "dev": true, 4083 - "license": "MIT", 4084 - "engines": { 4085 - "node": ">=0.6" 4086 } 4087 }, 4088 "node_modules/ts-api-utils": { ··· 4098 "typescript": ">=4.8.4" 4099 } 4100 }, 4101 - "node_modules/turbo-stream": { 4102 - "version": "2.4.0", 4103 - "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", 4104 - "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", 4105 - "license": "ISC" 4106 - }, 4107 "node_modules/type-check": { 4108 "version": "0.4.0", 4109 "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", ··· 4117 "node": ">= 0.8.0" 4118 } 4119 }, 4120 - "node_modules/type-is": { 4121 - "version": "2.0.1", 4122 - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", 4123 - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", 4124 - "dev": true, 4125 - "license": "MIT", 4126 - "dependencies": { 4127 - "content-type": "^1.0.5", 4128 - "media-typer": "^1.1.0", 4129 - "mime-types": "^3.0.0" 4130 - }, 4131 - "engines": { 4132 - "node": ">= 0.6" 4133 - } 4134 - }, 4135 "node_modules/typescript": { 4136 "version": "5.7.3", 4137 "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", ··· 4147 } 4148 }, 4149 "node_modules/typescript-eslint": { 4150 - "version": "8.31.1", 4151 - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.1.tgz", 4152 - "integrity": "sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA==", 4153 "dev": true, 4154 "license": "MIT", 4155 "dependencies": { 4156 - "@typescript-eslint/eslint-plugin": "8.31.1", 4157 - "@typescript-eslint/parser": "8.31.1", 4158 - "@typescript-eslint/utils": "8.31.1" 4159 }, 4160 "engines": { 4161 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 4170 } 4171 }, 4172 "node_modules/uint8arrays": { 4173 - "version": "3.0.0", 4174 - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", 4175 - "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", 4176 - "license": "MIT", 4177 "dependencies": { 4178 - "multiformats": "^9.4.2" 4179 } 4180 }, 4181 - "node_modules/unpipe": { 4182 - "version": "1.0.0", 4183 - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 4184 - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 4185 "dev": true, 4186 - "license": "MIT", 4187 - "engines": { 4188 - "node": ">= 0.8" 4189 - } 4190 }, 4191 "node_modules/update-browserslist-db": { 4192 "version": "1.1.3", ··· 4229 "punycode": "^2.1.0" 4230 } 4231 }, 4232 - "node_modules/vary": { 4233 - "version": "1.1.2", 4234 - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 4235 - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 4236 - "dev": true, 4237 - "license": "MIT", 4238 - "engines": { 4239 - "node": ">= 0.8" 4240 - } 4241 - }, 4242 "node_modules/vite": { 4243 - "version": "6.3.4", 4244 - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz", 4245 - "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==", 4246 "dev": true, 4247 "license": "MIT", 4248 "dependencies": { ··· 4315 } 4316 }, 4317 "node_modules/vite/node_modules/fdir": { 4318 - "version": "6.4.4", 4319 - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", 4320 - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", 4321 "dev": true, 4322 "license": "MIT", 4323 "peerDependencies": { ··· 4368 "node": ">=0.10.0" 4369 } 4370 }, 4371 - "node_modules/wrappy": { 4372 - "version": "1.0.2", 4373 - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 4374 - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 4375 "dev": true, 4376 - "license": "ISC" 4377 }, 4378 "node_modules/yallist": { 4379 "version": "3.1.1", ··· 4382 "dev": true, 4383 "license": "ISC" 4384 }, 4385 "node_modules/yocto-queue": { 4386 "version": "0.1.0", 4387 "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", ··· 4396 } 4397 }, 4398 "node_modules/zod": { 4399 - "version": "3.24.3", 4400 - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", 4401 - "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", 4402 "license": "MIT", 4403 "funding": { 4404 "url": "https://github.com/sponsors/colinhacks" 4405 - } 4406 - }, 4407 - "node_modules/zod-to-json-schema": { 4408 - "version": "3.24.5", 4409 - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", 4410 - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", 4411 - "dev": true, 4412 - "license": "ISC", 4413 - "peerDependencies": { 4414 - "zod": "^3.24.1" 4415 } 4416 } 4417 }
··· 1 { 2 "name": "atproto-migration", 3 + "version": "0.1.0", 4 "lockfileVersion": 3, 5 "requires": true, 6 "packages": { 7 "": { 8 "name": "atproto-migration", 9 + "version": "0.1.0", 10 "dependencies": { 11 "@atproto/api": "^0.15.5", 12 + "@atproto/crypto": "^0.4.4", 13 + "multiformats": "^13.3.6", 14 "react": "^19.0.0", 15 "react-dom": "^19.0.0", 16 + "react-router-dom": "^7.5.3", 17 + "uint8arrays": "^5.1.0" 18 }, 19 "devDependencies": { 20 + "@eslint/eslintrc": "^3.3.1", 21 "@eslint/js": "^9.22.0", 22 + "@humanwhocodes/module-importer": "^1.0.1", 23 + "@types/node": "^22.15.14", 24 + "@types/react": "^19.1.6", 25 + "@types/react-dom": "^19.1.5", 26 + "@typescript-eslint/eslint-plugin": "^8.33.0", 27 + "@typescript-eslint/parser": "^8.32.0", 28 "@vitejs/plugin-react": "^4.3.4", 29 "eslint": "^9.22.0", 30 "eslint-plugin-react-hooks": "^5.2.0", 31 "eslint-plugin-react-refresh": "^0.4.19", 32 "globals": "^16.0.0", 33 + "jiti": "^2.4.2", 34 + "rollup-plugin-visualizer": "^6.0.1", 35 + "terser": "^5.40.0", 36 "typescript": "~5.7.2", 37 "typescript-eslint": "^8.26.1", 38 "vite": "^6.3.1" ··· 53 } 54 }, 55 "node_modules/@atproto/api": { 56 + "version": "0.15.10", 57 + "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.15.10.tgz", 58 + "integrity": "sha512-/PsvYoYMA6VGAbMEOU2rOuaNQHkWPU6CVQAUDK2XRlIgFO2d21KEjZsZ4Z3lELvZlcw25fuMp7gLgFRijpk78w==", 59 "license": "MIT", 60 "dependencies": { 61 + "@atproto/common-web": "^0.4.2", 62 + "@atproto/lexicon": "^0.4.11", 63 "@atproto/syntax": "^0.4.0", 64 + "@atproto/xrpc": "^0.7.0", 65 "await-lock": "^2.2.2", 66 "multiformats": "^9.9.0", 67 "tlds": "^1.234.0", 68 "zod": "^3.23.8" 69 } 70 }, 71 + "node_modules/@atproto/api/node_modules/multiformats": { 72 + "version": "9.9.0", 73 + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", 74 + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 75 + "license": "(Apache-2.0 AND MIT)" 76 + }, 77 "node_modules/@atproto/common-web": { 78 + "version": "0.4.2", 79 + "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.2.tgz", 80 + "integrity": "sha512-vrXwGNoFGogodjQvJDxAeP3QbGtawgZute2ed1XdRO0wMixLk3qewtikZm06H259QDJVu6voKC5mubml+WgQUw==", 81 "license": "MIT", 82 "dependencies": { 83 "graphemer": "^1.4.0", ··· 86 "zod": "^3.23.8" 87 } 88 }, 89 + "node_modules/@atproto/common-web/node_modules/multiformats": { 90 + "version": "9.9.0", 91 + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", 92 + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 93 + "license": "(Apache-2.0 AND MIT)" 94 + }, 95 + "node_modules/@atproto/common-web/node_modules/uint8arrays": { 96 + "version": "3.0.0", 97 + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", 98 + "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", 99 + "license": "MIT", 100 + "dependencies": { 101 + "multiformats": "^9.4.2" 102 + } 103 + }, 104 + "node_modules/@atproto/crypto": { 105 + "version": "0.4.4", 106 + "resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.4.4.tgz", 107 + "integrity": "sha512-Yq9+crJ7WQl7sxStVpHgie5Z51R05etaK9DLWYG/7bR5T4bhdcIgF6IfklLShtZwLYdVVj+K15s0BqW9a8PSDA==", 108 + "license": "MIT", 109 + "dependencies": { 110 + "@noble/curves": "^1.7.0", 111 + "@noble/hashes": "^1.6.1", 112 + "uint8arrays": "3.0.0" 113 + }, 114 + "engines": { 115 + "node": ">=18.7.0" 116 + } 117 + }, 118 + "node_modules/@atproto/crypto/node_modules/multiformats": { 119 + "version": "9.9.0", 120 + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", 121 + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 122 + "license": "(Apache-2.0 AND MIT)" 123 + }, 124 + "node_modules/@atproto/crypto/node_modules/uint8arrays": { 125 + "version": "3.0.0", 126 + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", 127 + "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", 128 + "license": "MIT", 129 + "dependencies": { 130 + "multiformats": "^9.4.2" 131 + } 132 + }, 133 "node_modules/@atproto/lexicon": { 134 + "version": "0.4.11", 135 + "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.11.tgz", 136 + "integrity": "sha512-btefdnvNz2Ao2I+qbmj0F06HC8IlrM/IBz6qOBS50r0S6uDf5tOO+Mv2tSVdimFkdzyDdLtBI1sV36ONxz2cOw==", 137 "license": "MIT", 138 "dependencies": { 139 + "@atproto/common-web": "^0.4.2", 140 "@atproto/syntax": "^0.4.0", 141 "iso-datestring-validator": "^2.2.2", 142 "multiformats": "^9.9.0", 143 "zod": "^3.23.8" 144 } 145 }, 146 + "node_modules/@atproto/lexicon/node_modules/multiformats": { 147 + "version": "9.9.0", 148 + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", 149 + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 150 + "license": "(Apache-2.0 AND MIT)" 151 + }, 152 "node_modules/@atproto/syntax": { 153 "version": "0.4.0", 154 "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.0.tgz", ··· 156 "license": "MIT" 157 }, 158 "node_modules/@atproto/xrpc": { 159 + "version": "0.7.0", 160 + "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.0.tgz", 161 + "integrity": "sha512-SfhP9dGx2qclaScFDb58Jnrmim5nk4geZXCqg6sB0I/KZhZEkr9iIx1hLCp+sxkIfEsmEJjeWO4B0rjUIJW5cw==", 162 "license": "MIT", 163 "dependencies": { 164 + "@atproto/lexicon": "^0.4.11", 165 "zod": "^3.23.8" 166 } 167 }, ··· 181 } 182 }, 183 "node_modules/@babel/compat-data": { 184 + "version": "7.27.3", 185 + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", 186 + "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", 187 "dev": true, 188 "license": "MIT", 189 "engines": { ··· 191 } 192 }, 193 "node_modules/@babel/core": { 194 + "version": "7.27.3", 195 + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.3.tgz", 196 + "integrity": "sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==", 197 "dev": true, 198 "license": "MIT", 199 "dependencies": { 200 "@ampproject/remapping": "^2.2.0", 201 "@babel/code-frame": "^7.27.1", 202 + "@babel/generator": "^7.27.3", 203 + "@babel/helper-compilation-targets": "^7.27.2", 204 + "@babel/helper-module-transforms": "^7.27.3", 205 + "@babel/helpers": "^7.27.3", 206 + "@babel/parser": "^7.27.3", 207 + "@babel/template": "^7.27.2", 208 + "@babel/traverse": "^7.27.3", 209 + "@babel/types": "^7.27.3", 210 "convert-source-map": "^2.0.0", 211 "debug": "^4.1.0", 212 "gensync": "^1.0.0-beta.2", ··· 221 "url": "https://opencollective.com/babel" 222 } 223 }, 224 + "node_modules/@babel/core/node_modules/semver": { 225 + "version": "6.3.1", 226 + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 227 + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 228 + "dev": true, 229 + "license": "ISC", 230 + "bin": { 231 + "semver": "bin/semver.js" 232 + } 233 + }, 234 "node_modules/@babel/generator": { 235 + "version": "7.27.3", 236 + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", 237 + "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", 238 "dev": true, 239 "license": "MIT", 240 "dependencies": { 241 + "@babel/parser": "^7.27.3", 242 + "@babel/types": "^7.27.3", 243 "@jridgewell/gen-mapping": "^0.3.5", 244 "@jridgewell/trace-mapping": "^0.3.25", 245 "jsesc": "^3.0.2" ··· 249 } 250 }, 251 "node_modules/@babel/helper-compilation-targets": { 252 + "version": "7.27.2", 253 + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", 254 + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", 255 "dev": true, 256 "license": "MIT", 257 "dependencies": { 258 + "@babel/compat-data": "^7.27.2", 259 "@babel/helper-validator-option": "^7.27.1", 260 "browserslist": "^4.24.0", 261 "lru-cache": "^5.1.1", ··· 265 "node": ">=6.9.0" 266 } 267 }, 268 + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { 269 + "version": "6.3.1", 270 + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 271 + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 272 + "dev": true, 273 + "license": "ISC", 274 + "bin": { 275 + "semver": "bin/semver.js" 276 + } 277 + }, 278 "node_modules/@babel/helper-module-imports": { 279 "version": "7.27.1", 280 "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", ··· 290 } 291 }, 292 "node_modules/@babel/helper-module-transforms": { 293 + "version": "7.27.3", 294 + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", 295 + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", 296 "dev": true, 297 "license": "MIT", 298 "dependencies": { 299 "@babel/helper-module-imports": "^7.27.1", 300 "@babel/helper-validator-identifier": "^7.27.1", 301 + "@babel/traverse": "^7.27.3" 302 }, 303 "engines": { 304 "node": ">=6.9.0" ··· 348 } 349 }, 350 "node_modules/@babel/helpers": { 351 + "version": "7.27.3", 352 + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.3.tgz", 353 + "integrity": "sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==", 354 "dev": true, 355 "license": "MIT", 356 "dependencies": { 357 + "@babel/template": "^7.27.2", 358 + "@babel/types": "^7.27.3" 359 }, 360 "engines": { 361 "node": ">=6.9.0" 362 } 363 }, 364 "node_modules/@babel/parser": { 365 + "version": "7.27.3", 366 + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.3.tgz", 367 + "integrity": "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==", 368 "dev": true, 369 "license": "MIT", 370 "dependencies": { 371 + "@babel/types": "^7.27.3" 372 }, 373 "bin": { 374 "parser": "bin/babel-parser.js" ··· 410 } 411 }, 412 "node_modules/@babel/template": { 413 + "version": "7.27.2", 414 + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", 415 + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", 416 "dev": true, 417 "license": "MIT", 418 "dependencies": { 419 "@babel/code-frame": "^7.27.1", 420 + "@babel/parser": "^7.27.2", 421 "@babel/types": "^7.27.1" 422 }, 423 "engines": { ··· 425 } 426 }, 427 "node_modules/@babel/traverse": { 428 + "version": "7.27.3", 429 + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.3.tgz", 430 + "integrity": "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ==", 431 "dev": true, 432 "license": "MIT", 433 "dependencies": { 434 "@babel/code-frame": "^7.27.1", 435 + "@babel/generator": "^7.27.3", 436 + "@babel/parser": "^7.27.3", 437 + "@babel/template": "^7.27.2", 438 + "@babel/types": "^7.27.3", 439 "debug": "^4.3.1", 440 "globals": "^11.1.0" 441 }, ··· 454 } 455 }, 456 "node_modules/@babel/types": { 457 + "version": "7.27.3", 458 + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", 459 + "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", 460 "dev": true, 461 "license": "MIT", 462 "dependencies": { ··· 468 } 469 }, 470 "node_modules/@esbuild/aix-ppc64": { 471 + "version": "0.25.5", 472 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", 473 + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", 474 "cpu": [ 475 "ppc64" 476 ], ··· 485 } 486 }, 487 "node_modules/@esbuild/android-arm": { 488 + "version": "0.25.5", 489 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", 490 + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", 491 "cpu": [ 492 "arm" 493 ], ··· 502 } 503 }, 504 "node_modules/@esbuild/android-arm64": { 505 + "version": "0.25.5", 506 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", 507 + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", 508 "cpu": [ 509 "arm64" 510 ], ··· 519 } 520 }, 521 "node_modules/@esbuild/android-x64": { 522 + "version": "0.25.5", 523 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", 524 + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", 525 "cpu": [ 526 "x64" 527 ], ··· 536 } 537 }, 538 "node_modules/@esbuild/darwin-arm64": { 539 + "version": "0.25.5", 540 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", 541 + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", 542 "cpu": [ 543 "arm64" 544 ], ··· 553 } 554 }, 555 "node_modules/@esbuild/darwin-x64": { 556 + "version": "0.25.5", 557 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", 558 + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", 559 "cpu": [ 560 "x64" 561 ], ··· 570 } 571 }, 572 "node_modules/@esbuild/freebsd-arm64": { 573 + "version": "0.25.5", 574 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", 575 + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", 576 "cpu": [ 577 "arm64" 578 ], ··· 587 } 588 }, 589 "node_modules/@esbuild/freebsd-x64": { 590 + "version": "0.25.5", 591 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", 592 + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", 593 "cpu": [ 594 "x64" 595 ], ··· 604 } 605 }, 606 "node_modules/@esbuild/linux-arm": { 607 + "version": "0.25.5", 608 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", 609 + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", 610 "cpu": [ 611 "arm" 612 ], ··· 621 } 622 }, 623 "node_modules/@esbuild/linux-arm64": { 624 + "version": "0.25.5", 625 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", 626 + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", 627 "cpu": [ 628 "arm64" 629 ], ··· 638 } 639 }, 640 "node_modules/@esbuild/linux-ia32": { 641 + "version": "0.25.5", 642 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", 643 + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", 644 "cpu": [ 645 "ia32" 646 ], ··· 655 } 656 }, 657 "node_modules/@esbuild/linux-loong64": { 658 + "version": "0.25.5", 659 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", 660 + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", 661 "cpu": [ 662 "loong64" 663 ], ··· 672 } 673 }, 674 "node_modules/@esbuild/linux-mips64el": { 675 + "version": "0.25.5", 676 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", 677 + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", 678 "cpu": [ 679 "mips64el" 680 ], ··· 689 } 690 }, 691 "node_modules/@esbuild/linux-ppc64": { 692 + "version": "0.25.5", 693 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", 694 + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", 695 "cpu": [ 696 "ppc64" 697 ], ··· 706 } 707 }, 708 "node_modules/@esbuild/linux-riscv64": { 709 + "version": "0.25.5", 710 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", 711 + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", 712 "cpu": [ 713 "riscv64" 714 ], ··· 723 } 724 }, 725 "node_modules/@esbuild/linux-s390x": { 726 + "version": "0.25.5", 727 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", 728 + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", 729 "cpu": [ 730 "s390x" 731 ], ··· 740 } 741 }, 742 "node_modules/@esbuild/linux-x64": { 743 + "version": "0.25.5", 744 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", 745 + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", 746 "cpu": [ 747 "x64" 748 ], ··· 757 } 758 }, 759 "node_modules/@esbuild/netbsd-arm64": { 760 + "version": "0.25.5", 761 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", 762 + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", 763 "cpu": [ 764 "arm64" 765 ], ··· 774 } 775 }, 776 "node_modules/@esbuild/netbsd-x64": { 777 + "version": "0.25.5", 778 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", 779 + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", 780 "cpu": [ 781 "x64" 782 ], ··· 791 } 792 }, 793 "node_modules/@esbuild/openbsd-arm64": { 794 + "version": "0.25.5", 795 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", 796 + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", 797 "cpu": [ 798 "arm64" 799 ], ··· 808 } 809 }, 810 "node_modules/@esbuild/openbsd-x64": { 811 + "version": "0.25.5", 812 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", 813 + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", 814 "cpu": [ 815 "x64" 816 ], ··· 825 } 826 }, 827 "node_modules/@esbuild/sunos-x64": { 828 + "version": "0.25.5", 829 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", 830 + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", 831 "cpu": [ 832 "x64" 833 ], ··· 842 } 843 }, 844 "node_modules/@esbuild/win32-arm64": { 845 + "version": "0.25.5", 846 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", 847 + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", 848 "cpu": [ 849 "arm64" 850 ], ··· 859 } 860 }, 861 "node_modules/@esbuild/win32-ia32": { 862 + "version": "0.25.5", 863 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", 864 + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", 865 "cpu": [ 866 "ia32" 867 ], ··· 876 } 877 }, 878 "node_modules/@esbuild/win32-x64": { 879 + "version": "0.25.5", 880 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", 881 + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", 882 "cpu": [ 883 "x64" 884 ], ··· 911 "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 912 } 913 }, 914 "node_modules/@eslint-community/regexpp": { 915 "version": "4.12.1", 916 "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", ··· 947 } 948 }, 949 "node_modules/@eslint/core": { 950 + "version": "0.14.0", 951 + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", 952 + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", 953 "dev": true, 954 "license": "Apache-2.0", 955 "dependencies": { ··· 997 } 998 }, 999 "node_modules/@eslint/js": { 1000 + "version": "9.27.0", 1001 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", 1002 + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", 1003 "dev": true, 1004 "license": "MIT", 1005 "engines": { 1006 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1007 + }, 1008 + "funding": { 1009 + "url": "https://eslint.org/donate" 1010 } 1011 }, 1012 "node_modules/@eslint/object-schema": { ··· 1020 } 1021 }, 1022 "node_modules/@eslint/plugin-kit": { 1023 + "version": "0.3.1", 1024 + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", 1025 + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", 1026 "dev": true, 1027 "license": "Apache-2.0", 1028 "dependencies": { 1029 + "@eslint/core": "^0.14.0", 1030 "levn": "^0.4.1" 1031 }, 1032 "engines": { ··· 1086 } 1087 }, 1088 "node_modules/@humanwhocodes/retry": { 1089 + "version": "0.4.3", 1090 + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", 1091 + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", 1092 "dev": true, 1093 "license": "Apache-2.0", 1094 "engines": { ··· 1134 "node": ">=6.0.0" 1135 } 1136 }, 1137 + "node_modules/@jridgewell/source-map": { 1138 + "version": "0.3.6", 1139 + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", 1140 + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", 1141 + "dev": true, 1142 + "license": "MIT", 1143 + "dependencies": { 1144 + "@jridgewell/gen-mapping": "^0.3.5", 1145 + "@jridgewell/trace-mapping": "^0.3.25" 1146 + } 1147 + }, 1148 "node_modules/@jridgewell/sourcemap-codec": { 1149 "version": "1.5.0", 1150 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", ··· 1163 "@jridgewell/sourcemap-codec": "^1.4.14" 1164 } 1165 }, 1166 + "node_modules/@noble/curves": { 1167 + "version": "1.9.1", 1168 + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", 1169 + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", 1170 "license": "MIT", 1171 "dependencies": { 1172 + "@noble/hashes": "1.8.0" 1173 + }, 1174 + "engines": { 1175 + "node": "^14.21.3 || >=16" 1176 }, 1177 + "funding": { 1178 + "url": "https://paulmillr.com/funding/" 1179 + } 1180 + }, 1181 + "node_modules/@noble/hashes": { 1182 + "version": "1.8.0", 1183 + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", 1184 + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", 1185 + "license": "MIT", 1186 "engines": { 1187 + "node": "^14.21.3 || >=16" 1188 + }, 1189 + "funding": { 1190 + "url": "https://paulmillr.com/funding/" 1191 } 1192 }, 1193 "node_modules/@nodelib/fs.scandir": { ··· 1228 "node": ">= 8" 1229 } 1230 }, 1231 + "node_modules/@rolldown/pluginutils": { 1232 + "version": "1.0.0-beta.9", 1233 + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz", 1234 + "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==", 1235 + "dev": true, 1236 + "license": "MIT" 1237 + }, 1238 "node_modules/@rollup/rollup-android-arm-eabi": { 1239 + "version": "4.41.1", 1240 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", 1241 + "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", 1242 "cpu": [ 1243 "arm" 1244 ], ··· 1250 ] 1251 }, 1252 "node_modules/@rollup/rollup-android-arm64": { 1253 + "version": "4.41.1", 1254 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", 1255 + "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", 1256 "cpu": [ 1257 "arm64" 1258 ], ··· 1264 ] 1265 }, 1266 "node_modules/@rollup/rollup-darwin-arm64": { 1267 + "version": "4.41.1", 1268 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", 1269 + "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", 1270 "cpu": [ 1271 "arm64" 1272 ], ··· 1278 ] 1279 }, 1280 "node_modules/@rollup/rollup-darwin-x64": { 1281 + "version": "4.41.1", 1282 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", 1283 + "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", 1284 "cpu": [ 1285 "x64" 1286 ], ··· 1292 ] 1293 }, 1294 "node_modules/@rollup/rollup-freebsd-arm64": { 1295 + "version": "4.41.1", 1296 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", 1297 + "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", 1298 "cpu": [ 1299 "arm64" 1300 ], ··· 1306 ] 1307 }, 1308 "node_modules/@rollup/rollup-freebsd-x64": { 1309 + "version": "4.41.1", 1310 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", 1311 + "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", 1312 "cpu": [ 1313 "x64" 1314 ], ··· 1320 ] 1321 }, 1322 "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 1323 + "version": "4.41.1", 1324 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", 1325 + "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", 1326 "cpu": [ 1327 "arm" 1328 ], ··· 1334 ] 1335 }, 1336 "node_modules/@rollup/rollup-linux-arm-musleabihf": { 1337 + "version": "4.41.1", 1338 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", 1339 + "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", 1340 "cpu": [ 1341 "arm" 1342 ], ··· 1348 ] 1349 }, 1350 "node_modules/@rollup/rollup-linux-arm64-gnu": { 1351 + "version": "4.41.1", 1352 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", 1353 + "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", 1354 "cpu": [ 1355 "arm64" 1356 ], ··· 1362 ] 1363 }, 1364 "node_modules/@rollup/rollup-linux-arm64-musl": { 1365 + "version": "4.41.1", 1366 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", 1367 + "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", 1368 "cpu": [ 1369 "arm64" 1370 ], ··· 1376 ] 1377 }, 1378 "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 1379 + "version": "4.41.1", 1380 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", 1381 + "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", 1382 "cpu": [ 1383 "loong64" 1384 ], ··· 1390 ] 1391 }, 1392 "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 1393 + "version": "4.41.1", 1394 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", 1395 + "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", 1396 "cpu": [ 1397 "ppc64" 1398 ], ··· 1404 ] 1405 }, 1406 "node_modules/@rollup/rollup-linux-riscv64-gnu": { 1407 + "version": "4.41.1", 1408 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", 1409 + "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", 1410 "cpu": [ 1411 "riscv64" 1412 ], ··· 1418 ] 1419 }, 1420 "node_modules/@rollup/rollup-linux-riscv64-musl": { 1421 + "version": "4.41.1", 1422 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", 1423 + "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", 1424 "cpu": [ 1425 "riscv64" 1426 ], ··· 1432 ] 1433 }, 1434 "node_modules/@rollup/rollup-linux-s390x-gnu": { 1435 + "version": "4.41.1", 1436 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", 1437 + "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", 1438 "cpu": [ 1439 "s390x" 1440 ], ··· 1446 ] 1447 }, 1448 "node_modules/@rollup/rollup-linux-x64-gnu": { 1449 + "version": "4.41.1", 1450 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", 1451 + "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", 1452 "cpu": [ 1453 "x64" 1454 ], ··· 1460 ] 1461 }, 1462 "node_modules/@rollup/rollup-linux-x64-musl": { 1463 + "version": "4.41.1", 1464 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", 1465 + "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", 1466 "cpu": [ 1467 "x64" 1468 ], ··· 1474 ] 1475 }, 1476 "node_modules/@rollup/rollup-win32-arm64-msvc": { 1477 + "version": "4.41.1", 1478 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", 1479 + "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", 1480 "cpu": [ 1481 "arm64" 1482 ], ··· 1488 ] 1489 }, 1490 "node_modules/@rollup/rollup-win32-ia32-msvc": { 1491 + "version": "4.41.1", 1492 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", 1493 + "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", 1494 "cpu": [ 1495 "ia32" 1496 ], ··· 1502 ] 1503 }, 1504 "node_modules/@rollup/rollup-win32-x64-msvc": { 1505 + "version": "4.41.1", 1506 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", 1507 + "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", 1508 "cpu": [ 1509 "x64" 1510 ], ··· 1574 "dev": true, 1575 "license": "MIT" 1576 }, 1577 + "node_modules/@types/node": { 1578 + "version": "22.15.23", 1579 + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.23.tgz", 1580 + "integrity": "sha512-7Ec1zaFPF4RJ0eXu1YT/xgiebqwqoJz8rYPDi/O2BcZ++Wpt0Kq9cl0eg6NN6bYbPnR67ZLo7St5Q3UK0SnARw==", 1581 + "dev": true, 1582 + "license": "MIT", 1583 + "dependencies": { 1584 + "undici-types": "~6.21.0" 1585 + } 1586 + }, 1587 "node_modules/@types/react": { 1588 + "version": "19.1.6", 1589 + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz", 1590 + "integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==", 1591 "dev": true, 1592 "license": "MIT", 1593 "dependencies": { ··· 1595 } 1596 }, 1597 "node_modules/@types/react-dom": { 1598 + "version": "19.1.5", 1599 + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", 1600 + "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", 1601 "dev": true, 1602 "license": "MIT", 1603 "peerDependencies": { ··· 1605 } 1606 }, 1607 "node_modules/@typescript-eslint/eslint-plugin": { 1608 + "version": "8.33.0", 1609 + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz", 1610 + "integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==", 1611 "dev": true, 1612 "license": "MIT", 1613 "dependencies": { 1614 "@eslint-community/regexpp": "^4.10.0", 1615 + "@typescript-eslint/scope-manager": "8.33.0", 1616 + "@typescript-eslint/type-utils": "8.33.0", 1617 + "@typescript-eslint/utils": "8.33.0", 1618 + "@typescript-eslint/visitor-keys": "8.33.0", 1619 "graphemer": "^1.4.0", 1620 + "ignore": "^7.0.0", 1621 "natural-compare": "^1.4.0", 1622 + "ts-api-utils": "^2.1.0" 1623 }, 1624 "engines": { 1625 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1629 "url": "https://opencollective.com/typescript-eslint" 1630 }, 1631 "peerDependencies": { 1632 + "@typescript-eslint/parser": "^8.33.0", 1633 "eslint": "^8.57.0 || ^9.0.0", 1634 "typescript": ">=4.8.4 <5.9.0" 1635 } 1636 }, 1637 + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { 1638 + "version": "7.0.4", 1639 + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", 1640 + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", 1641 + "dev": true, 1642 + "license": "MIT", 1643 + "engines": { 1644 + "node": ">= 4" 1645 + } 1646 + }, 1647 "node_modules/@typescript-eslint/parser": { 1648 + "version": "8.33.0", 1649 + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.0.tgz", 1650 + "integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==", 1651 "dev": true, 1652 "license": "MIT", 1653 "dependencies": { 1654 + "@typescript-eslint/scope-manager": "8.33.0", 1655 + "@typescript-eslint/types": "8.33.0", 1656 + "@typescript-eslint/typescript-estree": "8.33.0", 1657 + "@typescript-eslint/visitor-keys": "8.33.0", 1658 "debug": "^4.3.4" 1659 }, 1660 "engines": { ··· 1669 "typescript": ">=4.8.4 <5.9.0" 1670 } 1671 }, 1672 + "node_modules/@typescript-eslint/project-service": { 1673 + "version": "8.33.0", 1674 + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz", 1675 + "integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==", 1676 + "dev": true, 1677 + "license": "MIT", 1678 + "dependencies": { 1679 + "@typescript-eslint/tsconfig-utils": "^8.33.0", 1680 + "@typescript-eslint/types": "^8.33.0", 1681 + "debug": "^4.3.4" 1682 + }, 1683 + "engines": { 1684 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1685 + }, 1686 + "funding": { 1687 + "type": "opencollective", 1688 + "url": "https://opencollective.com/typescript-eslint" 1689 + } 1690 + }, 1691 "node_modules/@typescript-eslint/scope-manager": { 1692 + "version": "8.33.0", 1693 + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz", 1694 + "integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==", 1695 "dev": true, 1696 "license": "MIT", 1697 "dependencies": { 1698 + "@typescript-eslint/types": "8.33.0", 1699 + "@typescript-eslint/visitor-keys": "8.33.0" 1700 }, 1701 "engines": { 1702 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1706 "url": "https://opencollective.com/typescript-eslint" 1707 } 1708 }, 1709 + "node_modules/@typescript-eslint/tsconfig-utils": { 1710 + "version": "8.33.0", 1711 + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz", 1712 + "integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==", 1713 + "dev": true, 1714 + "license": "MIT", 1715 + "engines": { 1716 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1717 + }, 1718 + "funding": { 1719 + "type": "opencollective", 1720 + "url": "https://opencollective.com/typescript-eslint" 1721 + }, 1722 + "peerDependencies": { 1723 + "typescript": ">=4.8.4 <5.9.0" 1724 + } 1725 + }, 1726 "node_modules/@typescript-eslint/type-utils": { 1727 + "version": "8.33.0", 1728 + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz", 1729 + "integrity": "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==", 1730 "dev": true, 1731 "license": "MIT", 1732 "dependencies": { 1733 + "@typescript-eslint/typescript-estree": "8.33.0", 1734 + "@typescript-eslint/utils": "8.33.0", 1735 "debug": "^4.3.4", 1736 + "ts-api-utils": "^2.1.0" 1737 }, 1738 "engines": { 1739 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1748 } 1749 }, 1750 "node_modules/@typescript-eslint/types": { 1751 + "version": "8.33.0", 1752 + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz", 1753 + "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==", 1754 "dev": true, 1755 "license": "MIT", 1756 "engines": { ··· 1762 } 1763 }, 1764 "node_modules/@typescript-eslint/typescript-estree": { 1765 + "version": "8.33.0", 1766 + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz", 1767 + "integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==", 1768 "dev": true, 1769 "license": "MIT", 1770 "dependencies": { 1771 + "@typescript-eslint/project-service": "8.33.0", 1772 + "@typescript-eslint/tsconfig-utils": "8.33.0", 1773 + "@typescript-eslint/types": "8.33.0", 1774 + "@typescript-eslint/visitor-keys": "8.33.0", 1775 "debug": "^4.3.4", 1776 "fast-glob": "^3.3.2", 1777 "is-glob": "^4.0.3", 1778 "minimatch": "^9.0.4", 1779 "semver": "^7.6.0", 1780 + "ts-api-utils": "^2.1.0" 1781 }, 1782 "engines": { 1783 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1816 "url": "https://github.com/sponsors/isaacs" 1817 } 1818 }, 1819 "node_modules/@typescript-eslint/utils": { 1820 + "version": "8.33.0", 1821 + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.0.tgz", 1822 + "integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==", 1823 "dev": true, 1824 "license": "MIT", 1825 "dependencies": { 1826 + "@eslint-community/eslint-utils": "^4.7.0", 1827 + "@typescript-eslint/scope-manager": "8.33.0", 1828 + "@typescript-eslint/types": "8.33.0", 1829 + "@typescript-eslint/typescript-estree": "8.33.0" 1830 }, 1831 "engines": { 1832 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1841 } 1842 }, 1843 "node_modules/@typescript-eslint/visitor-keys": { 1844 + "version": "8.33.0", 1845 + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz", 1846 + "integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==", 1847 "dev": true, 1848 "license": "MIT", 1849 "dependencies": { 1850 + "@typescript-eslint/types": "8.33.0", 1851 "eslint-visitor-keys": "^4.2.0" 1852 }, 1853 "engines": { ··· 1858 "url": "https://opencollective.com/typescript-eslint" 1859 } 1860 }, 1861 + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { 1862 + "version": "4.2.0", 1863 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", 1864 + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", 1865 + "dev": true, 1866 + "license": "Apache-2.0", 1867 + "engines": { 1868 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1869 + }, 1870 + "funding": { 1871 + "url": "https://opencollective.com/eslint" 1872 + } 1873 + }, 1874 "node_modules/@vitejs/plugin-react": { 1875 + "version": "4.5.0", 1876 + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz", 1877 + "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==", 1878 "dev": true, 1879 "license": "MIT", 1880 "dependencies": { 1881 "@babel/core": "^7.26.10", 1882 "@babel/plugin-transform-react-jsx-self": "^7.25.9", 1883 "@babel/plugin-transform-react-jsx-source": "^7.25.9", 1884 + "@rolldown/pluginutils": "1.0.0-beta.9", 1885 "@types/babel__core": "^7.20.5", 1886 "react-refresh": "^0.17.0" 1887 }, ··· 1892 "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" 1893 } 1894 }, 1895 "node_modules/acorn": { 1896 "version": "8.14.1", 1897 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", ··· 1932 "url": "https://github.com/sponsors/epoberezkin" 1933 } 1934 }, 1935 + "node_modules/ansi-regex": { 1936 + "version": "5.0.1", 1937 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1938 + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1939 + "dev": true, 1940 + "license": "MIT", 1941 + "engines": { 1942 + "node": ">=8" 1943 + } 1944 + }, 1945 "node_modules/ansi-styles": { 1946 "version": "4.3.0", 1947 "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", ··· 1978 "dev": true, 1979 "license": "MIT" 1980 }, 1981 "node_modules/brace-expansion": { 1982 "version": "1.1.11", 1983 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", ··· 2035 "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 2036 } 2037 }, 2038 + "node_modules/buffer-from": { 2039 + "version": "1.1.2", 2040 + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 2041 + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 2042 "dev": true, 2043 + "license": "MIT" 2044 }, 2045 "node_modules/callsites": { 2046 "version": "3.1.0", ··· 2053 } 2054 }, 2055 "node_modules/caniuse-lite": { 2056 + "version": "1.0.30001718", 2057 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", 2058 + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", 2059 "dev": true, 2060 "funding": [ 2061 { ··· 2090 "url": "https://github.com/chalk/chalk?sponsor=1" 2091 } 2092 }, 2093 + "node_modules/cliui": { 2094 + "version": "8.0.1", 2095 + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", 2096 + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", 2097 + "dev": true, 2098 + "license": "ISC", 2099 + "dependencies": { 2100 + "string-width": "^4.2.0", 2101 + "strip-ansi": "^6.0.1", 2102 + "wrap-ansi": "^7.0.0" 2103 + }, 2104 + "engines": { 2105 + "node": ">=12" 2106 + } 2107 + }, 2108 "node_modules/color-convert": { 2109 "version": "2.0.1", 2110 "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", ··· 2125 "dev": true, 2126 "license": "MIT" 2127 }, 2128 + "node_modules/commander": { 2129 + "version": "2.20.3", 2130 + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 2131 + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 2132 + "dev": true, 2133 + "license": "MIT" 2134 + }, 2135 "node_modules/concat-map": { 2136 "version": "0.0.1", 2137 "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", ··· 2139 "dev": true, 2140 "license": "MIT" 2141 }, 2142 "node_modules/convert-source-map": { 2143 "version": "2.0.0", 2144 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", ··· 2147 "license": "MIT" 2148 }, 2149 "node_modules/cookie": { 2150 + "version": "1.0.2", 2151 + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", 2152 + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", 2153 "license": "MIT", 2154 "engines": { 2155 + "node": ">=18" 2156 } 2157 }, 2158 "node_modules/cross-spawn": { ··· 2178 "license": "MIT" 2179 }, 2180 "node_modules/debug": { 2181 + "version": "4.4.1", 2182 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", 2183 + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", 2184 "dev": true, 2185 "license": "MIT", 2186 "dependencies": { ··· 2202 "dev": true, 2203 "license": "MIT" 2204 }, 2205 + "node_modules/define-lazy-prop": { 2206 "version": "2.0.0", 2207 + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", 2208 + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", 2209 "dev": true, 2210 "license": "MIT", 2211 "engines": { 2212 + "node": ">=8" 2213 } 2214 }, 2215 "node_modules/electron-to-chromium": { 2216 + "version": "1.5.159", 2217 + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.159.tgz", 2218 + "integrity": "sha512-CEvHptWAMV5p6GJ0Lq8aheyvVbfzVrv5mmidu1D3pidoVNkB3tTBsTMVtPJ+rzRK5oV229mCLz9Zj/hNvU8GBA==", 2219 "dev": true, 2220 "license": "ISC" 2221 }, 2222 + "node_modules/emoji-regex": { 2223 + "version": "8.0.0", 2224 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 2225 + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 2226 "dev": true, 2227 + "license": "MIT" 2228 }, 2229 "node_modules/esbuild": { 2230 + "version": "0.25.5", 2231 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", 2232 + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", 2233 "dev": true, 2234 "hasInstallScript": true, 2235 "license": "MIT", ··· 2240 "node": ">=18" 2241 }, 2242 "optionalDependencies": { 2243 + "@esbuild/aix-ppc64": "0.25.5", 2244 + "@esbuild/android-arm": "0.25.5", 2245 + "@esbuild/android-arm64": "0.25.5", 2246 + "@esbuild/android-x64": "0.25.5", 2247 + "@esbuild/darwin-arm64": "0.25.5", 2248 + "@esbuild/darwin-x64": "0.25.5", 2249 + "@esbuild/freebsd-arm64": "0.25.5", 2250 + "@esbuild/freebsd-x64": "0.25.5", 2251 + "@esbuild/linux-arm": "0.25.5", 2252 + "@esbuild/linux-arm64": "0.25.5", 2253 + "@esbuild/linux-ia32": "0.25.5", 2254 + "@esbuild/linux-loong64": "0.25.5", 2255 + "@esbuild/linux-mips64el": "0.25.5", 2256 + "@esbuild/linux-ppc64": "0.25.5", 2257 + "@esbuild/linux-riscv64": "0.25.5", 2258 + "@esbuild/linux-s390x": "0.25.5", 2259 + "@esbuild/linux-x64": "0.25.5", 2260 + "@esbuild/netbsd-arm64": "0.25.5", 2261 + "@esbuild/netbsd-x64": "0.25.5", 2262 + "@esbuild/openbsd-arm64": "0.25.5", 2263 + "@esbuild/openbsd-x64": "0.25.5", 2264 + "@esbuild/sunos-x64": "0.25.5", 2265 + "@esbuild/win32-arm64": "0.25.5", 2266 + "@esbuild/win32-ia32": "0.25.5", 2267 + "@esbuild/win32-x64": "0.25.5" 2268 } 2269 }, 2270 "node_modules/escalade": { ··· 2277 "node": ">=6" 2278 } 2279 }, 2280 "node_modules/escape-string-regexp": { 2281 "version": "4.0.0", 2282 "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", ··· 2291 } 2292 }, 2293 "node_modules/eslint": { 2294 + "version": "9.27.0", 2295 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", 2296 + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", 2297 "dev": true, 2298 "license": "MIT", 2299 "dependencies": { ··· 2301 "@eslint-community/regexpp": "^4.12.1", 2302 "@eslint/config-array": "^0.20.0", 2303 "@eslint/config-helpers": "^0.2.1", 2304 + "@eslint/core": "^0.14.0", 2305 "@eslint/eslintrc": "^3.3.1", 2306 + "@eslint/js": "9.27.0", 2307 + "@eslint/plugin-kit": "^0.3.1", 2308 "@humanfs/node": "^0.16.6", 2309 "@humanwhocodes/module-importer": "^1.0.1", 2310 "@humanwhocodes/retry": "^0.4.2", 2311 "@types/estree": "^1.0.6", 2312 "@types/json-schema": "^7.0.15", 2313 "ajv": "^6.12.4", ··· 2331 "lodash.merge": "^4.6.2", 2332 "minimatch": "^3.1.2", 2333 "natural-compare": "^1.4.0", 2334 + "optionator": "^0.9.3" 2335 }, 2336 "bin": { 2337 "eslint": "bin/eslint.js" ··· 2392 } 2393 }, 2394 "node_modules/eslint-visitor-keys": { 2395 + "version": "3.4.3", 2396 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 2397 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 2398 + "dev": true, 2399 + "license": "Apache-2.0", 2400 + "engines": { 2401 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 2402 + }, 2403 + "funding": { 2404 + "url": "https://opencollective.com/eslint" 2405 + } 2406 + }, 2407 + "node_modules/eslint/node_modules/eslint-visitor-keys": { 2408 "version": "4.2.0", 2409 "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", 2410 "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", ··· 2435 "url": "https://opencollective.com/eslint" 2436 } 2437 }, 2438 + "node_modules/espree/node_modules/eslint-visitor-keys": { 2439 + "version": "4.2.0", 2440 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", 2441 + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", 2442 + "dev": true, 2443 + "license": "Apache-2.0", 2444 + "engines": { 2445 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2446 + }, 2447 + "funding": { 2448 + "url": "https://opencollective.com/eslint" 2449 + } 2450 + }, 2451 "node_modules/esquery": { 2452 "version": "1.6.0", 2453 "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", ··· 2494 "node": ">=0.10.0" 2495 } 2496 }, 2497 "node_modules/fast-deep-equal": { 2498 "version": "3.1.3", 2499 "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", ··· 2581 "node": ">=8" 2582 } 2583 }, 2584 "node_modules/find-up": { 2585 "version": "5.0.0", 2586 "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", ··· 2619 "dev": true, 2620 "license": "ISC" 2621 }, 2622 "node_modules/fsevents": { 2623 "version": "2.3.3", 2624 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", ··· 2634 "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 2635 } 2636 }, 2637 "node_modules/gensync": { 2638 "version": "1.0.0-beta.2", 2639 "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", ··· 2644 "node": ">=6.9.0" 2645 } 2646 }, 2647 + "node_modules/get-caller-file": { 2648 + "version": "2.0.5", 2649 + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 2650 + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 2651 "dev": true, 2652 + "license": "ISC", 2653 "engines": { 2654 + "node": "6.* || 8.* || >= 10.*" 2655 } 2656 }, 2657 "node_modules/glob-parent": { ··· 2668 } 2669 }, 2670 "node_modules/globals": { 2671 + "version": "16.2.0", 2672 + "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", 2673 + "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==", 2674 "dev": true, 2675 "license": "MIT", 2676 "engines": { ··· 2680 "url": "https://github.com/sponsors/sindresorhus" 2681 } 2682 }, 2683 "node_modules/graphemer": { 2684 "version": "1.4.0", 2685 "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", ··· 2696 "node": ">=8" 2697 } 2698 }, 2699 "node_modules/ignore": { 2700 "version": "5.3.2", 2701 "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", ··· 2733 "node": ">=0.8.19" 2734 } 2735 }, 2736 + "node_modules/is-docker": { 2737 + "version": "2.2.1", 2738 + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", 2739 + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", 2740 "dev": true, 2741 "license": "MIT", 2742 + "bin": { 2743 + "is-docker": "cli.js" 2744 + }, 2745 "engines": { 2746 + "node": ">=8" 2747 + }, 2748 + "funding": { 2749 + "url": "https://github.com/sponsors/sindresorhus" 2750 } 2751 }, 2752 "node_modules/is-extglob": { ··· 2759 "node": ">=0.10.0" 2760 } 2761 }, 2762 + "node_modules/is-fullwidth-code-point": { 2763 + "version": "3.0.0", 2764 + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 2765 + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 2766 + "dev": true, 2767 + "license": "MIT", 2768 + "engines": { 2769 + "node": ">=8" 2770 + } 2771 + }, 2772 "node_modules/is-glob": { 2773 "version": "4.0.3", 2774 "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", ··· 2792 "node": ">=0.12.0" 2793 } 2794 }, 2795 + "node_modules/is-wsl": { 2796 + "version": "2.2.0", 2797 + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", 2798 + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", 2799 "dev": true, 2800 + "license": "MIT", 2801 + "dependencies": { 2802 + "is-docker": "^2.0.0" 2803 + }, 2804 + "engines": { 2805 + "node": ">=8" 2806 + } 2807 }, 2808 "node_modules/isexe": { 2809 "version": "2.0.0", ··· 2817 "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz", 2818 "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==", 2819 "license": "MIT" 2820 + }, 2821 + "node_modules/jiti": { 2822 + "version": "2.4.2", 2823 + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", 2824 + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", 2825 + "dev": true, 2826 + "license": "MIT", 2827 + "bin": { 2828 + "jiti": "lib/jiti-cli.mjs" 2829 + } 2830 }, 2831 "node_modules/js-tokens": { 2832 "version": "4.0.0", ··· 2952 "yallist": "^3.0.2" 2953 } 2954 }, 2955 "node_modules/merge2": { 2956 "version": "1.4.1", 2957 "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", ··· 2976 "node": ">=8.6" 2977 } 2978 }, 2979 "node_modules/minimatch": { 2980 "version": "3.1.2", 2981 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", ··· 2997 "license": "MIT" 2998 }, 2999 "node_modules/multiformats": { 3000 + "version": "13.3.6", 3001 + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.6.tgz", 3002 + "integrity": "sha512-yakbt9cPYj8d3vi/8o/XWm61MrOILo7fsTL0qxNx6zS0Nso6K5JqqS2WV7vK/KSuDBvrW3KfCwAdAgarAgOmww==", 3003 + "license": "Apache-2.0 OR MIT" 3004 }, 3005 "node_modules/nanoid": { 3006 "version": "3.3.11", ··· 3028 "dev": true, 3029 "license": "MIT" 3030 }, 3031 "node_modules/node-releases": { 3032 "version": "2.0.19", 3033 "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", ··· 3035 "dev": true, 3036 "license": "MIT" 3037 }, 3038 + "node_modules/open": { 3039 + "version": "8.4.2", 3040 + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", 3041 + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", 3042 "dev": true, 3043 "license": "MIT", 3044 + "dependencies": { 3045 + "define-lazy-prop": "^2.0.0", 3046 + "is-docker": "^2.1.1", 3047 + "is-wsl": "^2.2.0" 3048 + }, 3049 "engines": { 3050 + "node": ">=12" 3051 }, 3052 "funding": { 3053 + "url": "https://github.com/sponsors/sindresorhus" 3054 } 3055 }, 3056 "node_modules/optionator": { ··· 3116 "node": ">=6" 3117 } 3118 }, 3119 "node_modules/path-exists": { 3120 "version": "4.0.0", 3121 "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", ··· 3136 "node": ">=8" 3137 } 3138 }, 3139 "node_modules/picocolors": { 3140 "version": "1.1.1", 3141 "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", ··· 3154 }, 3155 "funding": { 3156 "url": "https://github.com/sponsors/jonschlinkert" 3157 } 3158 }, 3159 "node_modules/postcss": { ··· 3195 "node": ">= 0.8.0" 3196 } 3197 }, 3198 "node_modules/punycode": { 3199 "version": "2.3.1", 3200 "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", ··· 3205 "node": ">=6" 3206 } 3207 }, 3208 "node_modules/queue-microtask": { 3209 "version": "1.2.3", 3210 "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", ··· 3226 ], 3227 "license": "MIT" 3228 }, 3229 "node_modules/react": { 3230 "version": "19.1.0", 3231 "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", ··· 3258 } 3259 }, 3260 "node_modules/react-router": { 3261 + "version": "7.6.1", 3262 + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.1.tgz", 3263 + "integrity": "sha512-hPJXXxHJZEsPFNVbtATH7+MMX43UDeOauz+EAU4cgqTn7ojdI9qQORqS8Z0qmDlL1TclO/6jLRYUEtbWidtdHQ==", 3264 "license": "MIT", 3265 "dependencies": { 3266 "cookie": "^1.0.1", 3267 + "set-cookie-parser": "^2.6.0" 3268 }, 3269 "engines": { 3270 "node": ">=20.0.0" ··· 3280 } 3281 }, 3282 "node_modules/react-router-dom": { 3283 + "version": "7.6.1", 3284 + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.1.tgz", 3285 + "integrity": "sha512-vxU7ei//UfPYQ3iZvHuO1D/5fX3/JOqhNTbRR+WjSBWxf9bIvpWK+ftjmdfJHzPOuMQKe2fiEdG+dZX6E8uUpA==", 3286 "license": "MIT", 3287 "dependencies": { 3288 + "react-router": "7.6.1" 3289 }, 3290 "engines": { 3291 "node": ">=20.0.0" ··· 3295 "react-dom": ">=18" 3296 } 3297 }, 3298 + "node_modules/require-directory": { 3299 + "version": "2.1.1", 3300 + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 3301 + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 3302 + "dev": true, 3303 "license": "MIT", 3304 "engines": { 3305 + "node": ">=0.10.0" 3306 } 3307 }, 3308 "node_modules/resolve-from": { ··· 3327 } 3328 }, 3329 "node_modules/rollup": { 3330 + "version": "4.41.1", 3331 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", 3332 + "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", 3333 "dev": true, 3334 "license": "MIT", 3335 "dependencies": { ··· 3343 "npm": ">=8.0.0" 3344 }, 3345 "optionalDependencies": { 3346 + "@rollup/rollup-android-arm-eabi": "4.41.1", 3347 + "@rollup/rollup-android-arm64": "4.41.1", 3348 + "@rollup/rollup-darwin-arm64": "4.41.1", 3349 + "@rollup/rollup-darwin-x64": "4.41.1", 3350 + "@rollup/rollup-freebsd-arm64": "4.41.1", 3351 + "@rollup/rollup-freebsd-x64": "4.41.1", 3352 + "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", 3353 + "@rollup/rollup-linux-arm-musleabihf": "4.41.1", 3354 + "@rollup/rollup-linux-arm64-gnu": "4.41.1", 3355 + "@rollup/rollup-linux-arm64-musl": "4.41.1", 3356 + "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", 3357 + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", 3358 + "@rollup/rollup-linux-riscv64-gnu": "4.41.1", 3359 + "@rollup/rollup-linux-riscv64-musl": "4.41.1", 3360 + "@rollup/rollup-linux-s390x-gnu": "4.41.1", 3361 + "@rollup/rollup-linux-x64-gnu": "4.41.1", 3362 + "@rollup/rollup-linux-x64-musl": "4.41.1", 3363 + "@rollup/rollup-win32-arm64-msvc": "4.41.1", 3364 + "@rollup/rollup-win32-ia32-msvc": "4.41.1", 3365 + "@rollup/rollup-win32-x64-msvc": "4.41.1", 3366 "fsevents": "~2.3.2" 3367 } 3368 }, 3369 + "node_modules/rollup-plugin-visualizer": { 3370 + "version": "6.0.1", 3371 + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.1.tgz", 3372 + "integrity": "sha512-NjlGElvLXCSZSAi3gNRZbfX3qlQbQcJ9TW97c5JpqfVwMhttj9YwEdPwcvbKj91RnMX2PWAjonvSEv6UEYtnRQ==", 3373 "dev": true, 3374 "license": "MIT", 3375 "dependencies": { 3376 + "open": "^8.0.0", 3377 + "picomatch": "^4.0.2", 3378 + "source-map": "^0.7.4", 3379 + "yargs": "^17.5.1" 3380 + }, 3381 + "bin": { 3382 + "rollup-plugin-visualizer": "dist/bin/cli.js" 3383 }, 3384 "engines": { 3385 + "node": ">=18" 3386 + }, 3387 + "peerDependencies": { 3388 + "rolldown": "1.x", 3389 + "rollup": "2.x || 3.x || 4.x" 3390 + }, 3391 + "peerDependenciesMeta": { 3392 + "rolldown": { 3393 + "optional": true 3394 + }, 3395 + "rollup": { 3396 + "optional": true 3397 + } 3398 + } 3399 + }, 3400 + "node_modules/rollup-plugin-visualizer/node_modules/picomatch": { 3401 + "version": "4.0.2", 3402 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", 3403 + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", 3404 + "dev": true, 3405 + "license": "MIT", 3406 + "engines": { 3407 + "node": ">=12" 3408 + }, 3409 + "funding": { 3410 + "url": "https://github.com/sponsors/jonschlinkert" 3411 } 3412 }, 3413 "node_modules/run-parallel": { ··· 3434 "queue-microtask": "^1.2.2" 3435 } 3436 }, 3437 "node_modules/scheduler": { 3438 "version": "0.26.0", 3439 "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", ··· 3441 "license": "MIT" 3442 }, 3443 "node_modules/semver": { 3444 + "version": "7.7.2", 3445 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", 3446 + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", 3447 "dev": true, 3448 "license": "ISC", 3449 "bin": { 3450 "semver": "bin/semver.js" 3451 }, 3452 "engines": { 3453 + "node": ">=10" 3454 } 3455 }, 3456 "node_modules/set-cookie-parser": { ··· 3458 "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", 3459 "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", 3460 "license": "MIT" 3461 }, 3462 "node_modules/shebang-command": { 3463 "version": "2.0.0", ··· 3482 "node": ">=8" 3483 } 3484 }, 3485 + "node_modules/source-map": { 3486 + "version": "0.7.4", 3487 + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", 3488 + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", 3489 "dev": true, 3490 + "license": "BSD-3-Clause", 3491 "engines": { 3492 + "node": ">= 8" 3493 } 3494 }, 3495 + "node_modules/source-map-js": { 3496 + "version": "1.2.1", 3497 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 3498 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 3499 "dev": true, 3500 + "license": "BSD-3-Clause", 3501 "engines": { 3502 + "node": ">=0.10.0" 3503 } 3504 }, 3505 + "node_modules/source-map-support": { 3506 + "version": "0.5.21", 3507 + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 3508 + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 3509 "dev": true, 3510 "license": "MIT", 3511 "dependencies": { 3512 + "buffer-from": "^1.0.0", 3513 + "source-map": "^0.6.0" 3514 + } 3515 + }, 3516 + "node_modules/source-map-support/node_modules/source-map": { 3517 + "version": "0.6.1", 3518 + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 3519 + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 3520 + "dev": true, 3521 + "license": "BSD-3-Clause", 3522 "engines": { 3523 + "node": ">=0.10.0" 3524 } 3525 }, 3526 + "node_modules/string-width": { 3527 + "version": "4.2.3", 3528 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 3529 + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 3530 "dev": true, 3531 "license": "MIT", 3532 "dependencies": { 3533 + "emoji-regex": "^8.0.0", 3534 + "is-fullwidth-code-point": "^3.0.0", 3535 + "strip-ansi": "^6.0.1" 3536 }, 3537 "engines": { 3538 + "node": ">=8" 3539 } 3540 }, 3541 + "node_modules/strip-ansi": { 3542 + "version": "6.0.1", 3543 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 3544 + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 3545 "dev": true, 3546 "license": "MIT", 3547 + "dependencies": { 3548 + "ansi-regex": "^5.0.1" 3549 + }, 3550 "engines": { 3551 + "node": ">=8" 3552 } 3553 }, 3554 "node_modules/strip-json-comments": { ··· 3577 "node": ">=8" 3578 } 3579 }, 3580 + "node_modules/terser": { 3581 + "version": "5.40.0", 3582 + "resolved": "https://registry.npmjs.org/terser/-/terser-5.40.0.tgz", 3583 + "integrity": "sha512-cfeKl/jjwSR5ar7d0FGmave9hFGJT8obyo0z+CrQOylLDbk7X81nPU6vq9VORa5jU30SkDnT2FXjLbR8HLP+xA==", 3584 + "dev": true, 3585 + "license": "BSD-2-Clause", 3586 + "dependencies": { 3587 + "@jridgewell/source-map": "^0.3.3", 3588 + "acorn": "^8.14.0", 3589 + "commander": "^2.20.0", 3590 + "source-map-support": "~0.5.20" 3591 + }, 3592 + "bin": { 3593 + "terser": "bin/terser" 3594 + }, 3595 + "engines": { 3596 + "node": ">=10" 3597 + } 3598 + }, 3599 "node_modules/tinyglobby": { 3600 + "version": "0.2.14", 3601 + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", 3602 + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", 3603 "dev": true, 3604 "license": "MIT", 3605 "dependencies": { ··· 3614 } 3615 }, 3616 "node_modules/tinyglobby/node_modules/fdir": { 3617 + "version": "6.4.5", 3618 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", 3619 + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", 3620 "dev": true, 3621 "license": "MIT", 3622 "peerDependencies": { ··· 3642 } 3643 }, 3644 "node_modules/tlds": { 3645 + "version": "1.259.0", 3646 + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.259.0.tgz", 3647 + "integrity": "sha512-AldGGlDP0PNgwppe2quAvuBl18UcjuNtOnDuUkqhd6ipPqrYYBt3aTxK1QTsBVknk97lS2JcafWMghjGWFtunw==", 3648 "license": "MIT", 3649 "bin": { 3650 "tlds": "bin.js" ··· 3661 }, 3662 "engines": { 3663 "node": ">=8.0" 3664 } 3665 }, 3666 "node_modules/ts-api-utils": { ··· 3676 "typescript": ">=4.8.4" 3677 } 3678 }, 3679 "node_modules/type-check": { 3680 "version": "0.4.0", 3681 "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", ··· 3689 "node": ">= 0.8.0" 3690 } 3691 }, 3692 "node_modules/typescript": { 3693 "version": "5.7.3", 3694 "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", ··· 3704 } 3705 }, 3706 "node_modules/typescript-eslint": { 3707 + "version": "8.33.0", 3708 + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.0.tgz", 3709 + "integrity": "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ==", 3710 "dev": true, 3711 "license": "MIT", 3712 "dependencies": { 3713 + "@typescript-eslint/eslint-plugin": "8.33.0", 3714 + "@typescript-eslint/parser": "8.33.0", 3715 + "@typescript-eslint/utils": "8.33.0" 3716 }, 3717 "engines": { 3718 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 3727 } 3728 }, 3729 "node_modules/uint8arrays": { 3730 + "version": "5.1.0", 3731 + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", 3732 + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", 3733 + "license": "Apache-2.0 OR MIT", 3734 "dependencies": { 3735 + "multiformats": "^13.0.0" 3736 } 3737 }, 3738 + "node_modules/undici-types": { 3739 + "version": "6.21.0", 3740 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", 3741 + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", 3742 "dev": true, 3743 + "license": "MIT" 3744 }, 3745 "node_modules/update-browserslist-db": { 3746 "version": "1.1.3", ··· 3783 "punycode": "^2.1.0" 3784 } 3785 }, 3786 "node_modules/vite": { 3787 + "version": "6.3.5", 3788 + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", 3789 + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", 3790 "dev": true, 3791 "license": "MIT", 3792 "dependencies": { ··· 3859 } 3860 }, 3861 "node_modules/vite/node_modules/fdir": { 3862 + "version": "6.4.5", 3863 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", 3864 + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", 3865 "dev": true, 3866 "license": "MIT", 3867 "peerDependencies": { ··· 3912 "node": ">=0.10.0" 3913 } 3914 }, 3915 + "node_modules/wrap-ansi": { 3916 + "version": "7.0.0", 3917 + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 3918 + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 3919 + "dev": true, 3920 + "license": "MIT", 3921 + "dependencies": { 3922 + "ansi-styles": "^4.0.0", 3923 + "string-width": "^4.1.0", 3924 + "strip-ansi": "^6.0.0" 3925 + }, 3926 + "engines": { 3927 + "node": ">=10" 3928 + }, 3929 + "funding": { 3930 + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 3931 + } 3932 + }, 3933 + "node_modules/y18n": { 3934 + "version": "5.0.8", 3935 + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 3936 + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 3937 "dev": true, 3938 + "license": "ISC", 3939 + "engines": { 3940 + "node": ">=10" 3941 + } 3942 }, 3943 "node_modules/yallist": { 3944 "version": "3.1.1", ··· 3947 "dev": true, 3948 "license": "ISC" 3949 }, 3950 + "node_modules/yargs": { 3951 + "version": "17.7.2", 3952 + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", 3953 + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", 3954 + "dev": true, 3955 + "license": "MIT", 3956 + "dependencies": { 3957 + "cliui": "^8.0.1", 3958 + "escalade": "^3.1.1", 3959 + "get-caller-file": "^2.0.5", 3960 + "require-directory": "^2.1.1", 3961 + "string-width": "^4.2.3", 3962 + "y18n": "^5.0.5", 3963 + "yargs-parser": "^21.1.1" 3964 + }, 3965 + "engines": { 3966 + "node": ">=12" 3967 + } 3968 + }, 3969 + "node_modules/yargs-parser": { 3970 + "version": "21.1.1", 3971 + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", 3972 + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", 3973 + "dev": true, 3974 + "license": "ISC", 3975 + "engines": { 3976 + "node": ">=12" 3977 + } 3978 + }, 3979 "node_modules/yocto-queue": { 3980 "version": "0.1.0", 3981 "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", ··· 3990 } 3991 }, 3992 "node_modules/zod": { 3993 + "version": "3.25.32", 3994 + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.32.tgz", 3995 + "integrity": "sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g==", 3996 "license": "MIT", 3997 "funding": { 3998 "url": "https://github.com/sponsors/colinhacks" 3999 } 4000 } 4001 }
+16 -5
package.json
··· 1 { 2 "name": "atproto-migration", 3 "private": true, 4 - "version": "0.0.0", 5 "type": "module", 6 "scripts": { 7 "dev": "vite", 8 "build": "tsc -b && vite build", 9 - "lint": "eslint .", 10 "preview": "vite preview" 11 }, 12 "dependencies": { 13 "@atproto/api": "^0.15.5", 14 "react": "^19.0.0", 15 "react-dom": "^19.0.0", 16 - "react-router-dom": "^7.5.3" 17 }, 18 "devDependencies": { 19 "@eslint/js": "^9.22.0", 20 - "@types/react": "^19.0.10", 21 - "@types/react-dom": "^19.0.4", 22 "@vitejs/plugin-react": "^4.3.4", 23 "eslint": "^9.22.0", 24 "eslint-plugin-react-hooks": "^5.2.0", 25 "eslint-plugin-react-refresh": "^0.4.19", 26 "globals": "^16.0.0", 27 "typescript": "~5.7.2", 28 "typescript-eslint": "^8.26.1", 29 "vite": "^6.3.1"
··· 1 { 2 "name": "atproto-migration", 3 "private": true, 4 + "version": "0.1.0", 5 "type": "module", 6 "scripts": { 7 "dev": "vite", 8 "build": "tsc -b && vite build", 9 + "lint": "eslint src/", 10 "preview": "vite preview" 11 }, 12 "dependencies": { 13 "@atproto/api": "^0.15.5", 14 + "@atproto/crypto": "^0.4.4", 15 + "multiformats": "^13.3.6", 16 "react": "^19.0.0", 17 "react-dom": "^19.0.0", 18 + "react-router-dom": "^7.5.3", 19 + "uint8arrays": "^5.1.0" 20 }, 21 "devDependencies": { 22 + "@eslint/eslintrc": "^3.3.1", 23 "@eslint/js": "^9.22.0", 24 + "@humanwhocodes/module-importer": "^1.0.1", 25 + "@types/node": "^22.15.14", 26 + "@types/react": "^19.1.6", 27 + "@types/react-dom": "^19.1.5", 28 + "@typescript-eslint/eslint-plugin": "^8.33.0", 29 + "@typescript-eslint/parser": "^8.32.0", 30 "@vitejs/plugin-react": "^4.3.4", 31 "eslint": "^9.22.0", 32 "eslint-plugin-react-hooks": "^5.2.0", 33 "eslint-plugin-react-refresh": "^0.4.19", 34 "globals": "^16.0.0", 35 + "jiti": "^2.4.2", 36 + "rollup-plugin-visualizer": "^6.0.1", 37 + "terser": "^5.40.0", 38 "typescript": "~5.7.2", 39 "typescript-eslint": "^8.26.1", 40 "vite": "^6.3.1"
+37
public/favicon.svg
···
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <svg 3 + width="256" 4 + height="256" 5 + viewBox="0 0 32 32" 6 + fill="none" 7 + version="1.1" 8 + id="svg5" 9 + xmlns="http://www.w3.org/2000/svg" 10 + xmlns:svg="http://www.w3.org/2000/svg"> 11 + <path 12 + d="M24 8H8C6.89543 8 6 8.89543 6 10V24C6 25.1046 6.89543 26 8 26H24C25.1046 26 26 25.1046 26 24V10C26 8.89543 25.1046 8 24 8Z" 13 + stroke="#3b82f6" 14 + stroke-width="2" 15 + stroke-linecap="round" 16 + stroke-linejoin="round" 17 + id="path1" /> 18 + <path 19 + d="M20 8V6C20 4.89543 19.1046 4 18 4H14C12.8954 4 12 4.89543 12 6V8" 20 + stroke="#3b82f6" 21 + stroke-width="2" 22 + stroke-linecap="round" 23 + stroke-linejoin="round" 24 + id="path2" /> 25 + <path 26 + d="M6 12H26" 27 + stroke="#3b82f6" 28 + stroke-width="2" 29 + stroke-linecap="round" 30 + stroke-linejoin="round" 31 + id="path3" /> 32 + <path 33 + style="font-weight:bold;font-size:12px;font-family:Galvji;-inkscape-font-specification:'Galvji, Bold';fill:#3b82f6;stroke-width:0.250394" 34 + d="m 15.861771,17.466183 q -0.46875,0 -0.732422,0.351562 -0.257812,0.351563 -0.257812,0.984375 0,0.626953 0.257812,0.984375 0.263672,0.351563 0.726563,0.351563 0.474609,0 0.74414,-0.357422 0.275391,-0.357422 0.275391,-0.978516 0,-0.621094 -0.275391,-0.978515 -0.269531,-0.357422 -0.738281,-0.357422 z m 0.1875,-3.591797 q 1.054688,0 1.921875,0.316406 0.873047,0.316406 1.494141,0.890625 0.626953,0.574219 0.966797,1.376953 0.345703,0.802735 0.345703,1.769531 0,0.697266 -0.164063,1.259766 -0.158203,0.5625 -0.457031,0.955078 -0.298828,0.392578 -0.726562,0.603516 -0.421875,0.210937 -0.955079,0.210937 -0.550781,0 -0.925781,-0.246093 -0.36914,-0.246094 -0.46875,-0.667969 h -0.105469 q -0.375,0.896484 -1.40625,0.896484 -0.457031,0 -0.832031,-0.175781 -0.375,-0.175781 -0.638672,-0.498047 -0.263672,-0.328125 -0.410156,-0.785156 -0.146484,-0.457031 -0.146484,-1.013672 0,-0.533203 0.140625,-0.972656 0.146484,-0.439453 0.404297,-0.75586 0.257812,-0.316406 0.621093,-0.486328 0.363282,-0.175781 0.802735,-0.175781 0.474609,0 0.832031,0.216797 0.363281,0.216797 0.539062,0.609375 h 0.105469 v -0.697266 h 1.183594 v 3.105469 q 0,0.316406 0.146484,0.486328 0.146485,0.169922 0.416016,0.169922 0.222656,0 0.398437,-0.134766 0.181641,-0.134765 0.310547,-0.386718 0.128907,-0.251954 0.19336,-0.609375 0.07031,-0.363282 0.07031,-0.808594 0,-0.796875 -0.263672,-1.441406 -0.263672,-0.650391 -0.75,-1.107422 -0.480468,-0.457032 -1.166015,-0.703125 -0.679688,-0.251953 -1.511719,-0.251953 -0.849609,0 -1.558594,0.292968 -0.703125,0.28711 -1.21289,0.814453 -0.509766,0.521485 -0.791016,1.253907 -0.28125,0.726562 -0.28125,1.605468 0,0.890625 0.28125,1.611329 0.287109,0.714843 0.814453,1.21875 0.533203,0.503906 1.283203,0.773437 0.75586,0.275391 1.6875,0.275391 0.234375,0 0.462891,-0.01758 0.234375,-0.01172 0.445312,-0.03516 0.210938,-0.02344 0.386719,-0.05859 0.175781,-0.03516 0.298828,-0.08203 v 0.955078 q -0.345703,0.09961 -0.779297,0.152344 -0.433593,0.05859 -0.902343,0.05859 -1.142579,0 -2.080079,-0.351563 -0.93164,-0.345703 -1.59375,-0.984375 -0.65625,-0.638671 -1.019531,-1.541015 -0.357422,-0.902344 -0.357422,-2.003906 0,-1.083985 0.357422,-1.980469 0.363281,-0.902344 1.019531,-1.546875 0.65625,-0.644531 1.564454,-1.001953 0.908203,-0.357422 2.009765,-0.357422 z" 35 + id="text5" 36 + aria-label="@" /> 37 + </svg>
-1
public/vite.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
···
-172
src/App.tsx
··· 1 - import { useState, useEffect } from 'react' 2 - import { BrowserRouter as Router, Routes, Route, Navigate, useLocation } from 'react-router-dom' 3 - import { AtpAgent } from '@atproto/api' 4 - import { AvatarProvider } from './contexts/AvatarContext' 5 - import { NetworkProvider } from './contexts/NetworkContext' 6 - import NetworkWarning from './components/common/NetworkWarning' 7 - import Login from './components/auth/Login' 8 - import Actions from './components/common/Actions' 9 - import Migration from './components/common/Migration' 10 - import MigrationProcess from './components/common/MigrationProcess' 11 - import RecoveryKey from './components/common/RecoveryKey' 12 - import RecoveryKeyProcess from './components/common/RecoveryKeyProcess' 13 - import './styles/App.css' 14 - 15 - const SESSION_KEY = 'atproto_session'; 16 - const SESSION_EXPIRY = 60 * 60 * 1000; // 1 hour in milliseconds 17 - 18 - function AppRoutes({ agent, onLogout, handleLogin }: { 19 - agent: AtpAgent | null; 20 - onLogout: () => void; 21 - handleLogin: (agent: AtpAgent) => void; 22 - }) { 23 - const location = useLocation(); 24 - 25 - useEffect(() => { 26 - const checkSession = async () => { 27 - if (agent) { 28 - try { 29 - // Try to make a simple API call to verify the session 30 - await agent.getProfile({ actor: agent.session?.handle || '' }); 31 - } catch (err) { 32 - // If the API call fails, the session is likely invalid 33 - onLogout(); 34 - alert('Your session has expired. Please log in again.'); 35 - } 36 - } 37 - }; 38 - 39 - checkSession(); 40 - }, [location.pathname, agent, onLogout]); 41 - 42 - return ( 43 - <> 44 - <NetworkWarning /> 45 - <Routes> 46 - <Route 47 - path="/" 48 - element={ 49 - agent ? ( 50 - <Navigate to="/actions" replace /> 51 - ) : ( 52 - <Login onLogin={handleLogin} /> 53 - ) 54 - } 55 - /> 56 - <Route 57 - path="/actions" 58 - element={ 59 - agent ? ( 60 - <Actions agent={agent} onLogout={onLogout} /> 61 - ) : ( 62 - <Navigate to="/" replace /> 63 - ) 64 - } 65 - /> 66 - <Route 67 - path="/migration" 68 - element={ 69 - agent ? ( 70 - <Migration agent={agent} onLogout={onLogout} /> 71 - ) : ( 72 - <Navigate to="/" replace /> 73 - ) 74 - } 75 - /> 76 - <Route 77 - path="/migration/process" 78 - element={ 79 - agent ? ( 80 - <MigrationProcess agent={agent} onLogout={onLogout} /> 81 - ) : ( 82 - <Navigate to="/" replace /> 83 - ) 84 - } 85 - /> 86 - <Route 87 - path="/recovery-key" 88 - element={ 89 - agent ? ( 90 - <RecoveryKey agent={agent} onLogout={onLogout} /> 91 - ) : ( 92 - <Navigate to="/" replace /> 93 - ) 94 - } 95 - /> 96 - <Route 97 - path="/recovery-key/process" 98 - element={ 99 - agent ? ( 100 - <RecoveryKeyProcess agent={agent} onLogout={onLogout} /> 101 - ) : ( 102 - <Navigate to="/" replace /> 103 - ) 104 - } 105 - /> 106 - </Routes> 107 - </> 108 - ); 109 - } 110 - 111 - function App() { 112 - const [agent, setAgent] = useState<AtpAgent | null>(null) 113 - 114 - useEffect(() => { 115 - // Load session from localStorage on initial load 116 - const loadSession = async () => { 117 - const savedSession = localStorage.getItem(SESSION_KEY); 118 - if (savedSession) { 119 - const { session, service, timestamp } = JSON.parse(savedSession); 120 - 121 - // Check if session is expired 122 - if (Date.now() - timestamp > SESSION_EXPIRY) { 123 - localStorage.removeItem(SESSION_KEY); 124 - return; 125 - } 126 - 127 - const newAgent = new AtpAgent({ service }); 128 - await newAgent.resumeSession(session); 129 - setAgent(newAgent); 130 - } 131 - }; 132 - 133 - loadSession(); 134 - }, []); 135 - 136 - const handleLogin = (newAgent: AtpAgent) => { 137 - setAgent(newAgent); 138 - // Save session to localStorage 139 - localStorage.setItem(SESSION_KEY, JSON.stringify({ 140 - session: newAgent.session, 141 - service: newAgent.service.toString(), 142 - timestamp: Date.now() 143 - })); 144 - }; 145 - 146 - const handleLogout = () => { 147 - setAgent(null); 148 - localStorage.removeItem(SESSION_KEY); 149 - // Clear avatar URL from context 150 - const avatarContext = document.querySelector('[data-avatar-context]'); 151 - if (avatarContext) { 152 - const event = new CustomEvent('clearAvatar'); 153 - avatarContext.dispatchEvent(event); 154 - } 155 - }; 156 - 157 - return ( 158 - <NetworkProvider> 159 - <AvatarProvider> 160 - <Router> 161 - <AppRoutes 162 - agent={agent} 163 - onLogout={handleLogout} 164 - handleLogin={handleLogin} 165 - /> 166 - </Router> 167 - </AvatarProvider> 168 - </NetworkProvider> 169 - ) 170 - } 171 - 172 - export default App
···
-189
src/components/auth/Login.tsx
··· 1 - import { useState } from 'react'; 2 - import { useNavigate } from 'react-router-dom'; 3 - import { AtpAgent } from '@atproto/api'; 4 - import Footer from '../layout/Footer'; 5 - import '../../styles/App.css'; 6 - 7 - interface LoginProps { 8 - onLogin: (agent: AtpAgent) => void; 9 - } 10 - 11 - interface DidDocument { 12 - service: Array<{ 13 - id: string; 14 - type: string; 15 - serviceEndpoint: string; 16 - }>; 17 - } 18 - 19 - type LoginStep = 'idle' | 'resolving-handle' | 'resolving-did' | 'connecting-pds' | 'authenticating' | 'success'; 20 - 21 - export default function Login({ onLogin }: LoginProps) { 22 - const [handle, setHandle] = useState(''); 23 - const [password, setPassword] = useState(''); 24 - const [error, setError] = useState(''); 25 - const [appPasswordAttempts, setAppPasswordAttempts] = useState(0); 26 - const [loginStep, setLoginStep] = useState<LoginStep>('idle'); 27 - const navigate = useNavigate(); 28 - 29 - const isAppPassword = (password: string) => { 30 - // App passwords are typically in the format xxxx-xxxx-xxxx-xxxx 31 - return /^[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$/.test(password); 32 - }; 33 - 34 - const getStepMessage = (step: LoginStep) => { 35 - switch (step) { 36 - case 'resolving-handle': 37 - return 'Resolving your handle...'; 38 - case 'resolving-did': 39 - return 'Resolving your DID...'; 40 - case 'connecting-pds': 41 - return 'Connecting to your Personal Data Server...'; 42 - case 'authenticating': 43 - return 'Authenticating your credentials...'; 44 - case 'success': 45 - return 'Login successful! Redirecting...'; 46 - default: 47 - return ''; 48 - } 49 - }; 50 - 51 - const handleSubmit = async (e: React.FormEvent) => { 52 - e.preventDefault(); 53 - setError(''); 54 - setLoginStep('resolving-handle'); 55 - 56 - // app password check and debug method 57 - if (isAppPassword(password)) { 58 - if (appPasswordAttempts < 3) { 59 - setAppPasswordAttempts(appPasswordAttempts + 1); 60 - setError(`You have entered an app password, which does not allow for you to migrate your account. Please enter your main account password instead.`); 61 - setLoginStep('idle'); 62 - return; 63 - } 64 - } 65 - 66 - setHandle(handle.trim()); 67 - 68 - try { 69 - // Create temporary agent to resolve DID 70 - const tempAgent = new AtpAgent({ service: 'https://public.api.bsky.app' }); 71 - 72 - // Get DID document from handle 73 - setLoginStep('resolving-handle'); 74 - const didResponse = await tempAgent.com.atproto.identity.resolveHandle({ 75 - handle: handle 76 - }); 77 - 78 - if (!didResponse.success) { 79 - // Try did:web resolution first 80 - const domain = handle.split('.').join(':'); 81 - const webDid = `did:web:${domain}`; 82 - try { 83 - const webResponse = await fetch(`https://${handle}/.well-known/did.json`); 84 - if (webResponse.ok) { 85 - // If successful, continue with the did:web 86 - didResponse.data.did = webDid; 87 - } else { 88 - throw new Error('Invalid handle'); 89 - } 90 - } catch { 91 - throw new Error('Invalid handle'); 92 - } 93 - } 94 - 95 - // Get PDS endpoint from DID document 96 - setLoginStep('resolving-did'); 97 - let didDocResponse; 98 - const did = didResponse.data.did; 99 - 100 - if (did.startsWith('did:plc:')) { 101 - // For PLC DIDs, resolve from plc.directory 102 - const plcResponse = await fetch(`https://plc.directory/${did}`); 103 - didDocResponse = { data: await plcResponse.json() }; 104 - } else if (did.startsWith('did:web:')) { 105 - // For Web DIDs, get from .well-known/did.json 106 - const domain = did.split(':')[2]; 107 - const webResponse = await fetch(`https://${domain}/.well-known/did.json`); 108 - didDocResponse = { data: await webResponse.json() }; 109 - } else { 110 - // Fallback to ATP resolver for other DID types 111 - didDocResponse = await tempAgent.com.atproto.identity.resolveDid({ 112 - did: did 113 - }); 114 - } 115 - 116 - setLoginStep('connecting-pds'); 117 - const pds = ((didDocResponse.data as unknown) as DidDocument).service.find((s) => s.id === '#atproto_pds')?.serviceEndpoint || 'https://bsky.social'; 118 - 119 - const agent = new AtpAgent({ service: pds }); 120 - 121 - setLoginStep('authenticating'); 122 - await agent.login({ identifier: handle, password }); 123 - 124 - setLoginStep('success'); 125 - onLogin(agent); 126 - navigate('/actions'); 127 - } catch (err) { 128 - setError(err instanceof Error ? err.message : 'Login failed'); 129 - setLoginStep('idle'); 130 - } 131 - }; 132 - 133 - return ( 134 - <div> 135 - <h1 className="login-title">ATproto Migrator</h1> 136 - <div className="login-container"> 137 - <div className="login-card"> 138 - <h2 className="login-title">Sign in to your account</h2> 139 - <div className="warning-message"> 140 - โš ๏ธ Please use your main account password, not an app password. All operations are performed locally in your browser. 141 - </div> 142 - <div className="warning-message"> 143 - ALSO This tool currently does not do anything, it should be done SOONโ„ข๏ธ 144 - </div> 145 - <form className="login-form" onSubmit={handleSubmit}> 146 - <div className="form-group"> 147 - <input 148 - type="text" 149 - required 150 - className="form-input" 151 - placeholder="Handle (e.g., example.bsky.social)" 152 - value={handle} 153 - onChange={(e) => setHandle(e.target.value)} 154 - disabled={loginStep !== 'idle'} 155 - /> 156 - </div> 157 - <div className="form-group"> 158 - <input 159 - type="password" 160 - required 161 - className="form-input" 162 - placeholder="Password" 163 - value={password} 164 - onChange={(e) => setPassword(e.target.value)} 165 - disabled={loginStep !== 'idle'} 166 - /> 167 - </div> 168 - 169 - {error && <div className="error-message">{error}</div>} 170 - {loginStep !== 'idle' && ( 171 - <div className="loading-message"> 172 - {getStepMessage(loginStep)} 173 - </div> 174 - )} 175 - 176 - <button 177 - type="submit" 178 - className="submit-button" 179 - disabled={loginStep !== 'idle'} 180 - > 181 - {loginStep === 'idle' ? 'Sign in' : 'Signing in...'} 182 - </button> 183 - </form> 184 - </div> 185 - </div> 186 - <Footer /> 187 - </div> 188 - ); 189 - }
···
-131
src/components/common/Actions.tsx
··· 1 - import { useEffect, useState } from 'react'; 2 - import { AtpAgent } from '@atproto/api'; 3 - import { useNavigate } from 'react-router-dom'; 4 - import Footer from '../layout/Footer'; 5 - import Header from '../layout/Header'; 6 - import '../../styles/App.css'; 7 - 8 - interface ActionsProps { 9 - agent: AtpAgent; 10 - onLogout: () => void; 11 - } 12 - 13 - export default function Actions({ agent, onLogout }: ActionsProps) { 14 - const [didDoc, setDidDoc] = useState<string>(''); 15 - const [loading, setLoading] = useState(true); 16 - const navigate = useNavigate(); 17 - 18 - const handleLogout = () => { 19 - onLogout(); 20 - navigate('/'); 21 - }; 22 - 23 - useEffect(() => { 24 - const fetchProfile = async () => { 25 - try { 26 - const did = agent.session?.did; 27 - if (!did) { 28 - throw new Error('No DID found in session'); 29 - } 30 - 31 - let didDocResponse; 32 - 33 - if (did.startsWith('did:plc:')) { 34 - // For PLC DIDs, resolve from plc.directory 35 - const response = await fetch(`https://plc.directory/${did}`); 36 - didDocResponse = await response.json(); 37 - } else if (did.startsWith('did:web:')) { 38 - // For Web DIDs, get from .well-known/did.json 39 - const domain = did.split(':')[2]; 40 - const response = await fetch(`https://${domain}/.well-known/did.json`); 41 - didDocResponse = await response.json(); 42 - } else { 43 - throw new Error(`Unsupported DID type: ${did}`); 44 - } 45 - 46 - setDidDoc(JSON.stringify(didDocResponse, null, 2)); 47 - } catch (err) { 48 - console.error('Error fetching DID document:', err); 49 - setDidDoc(`Error fetching DID document: ${err instanceof Error ? err.message : 'Unknown error'}`); 50 - } finally { 51 - setLoading(false); 52 - } 53 - }; 54 - 55 - fetchProfile(); 56 - }, [agent]); 57 - 58 - if (loading) { 59 - return ( 60 - <div className="loading-container"> 61 - <div className="loading-text">Loading...</div> 62 - </div> 63 - ); 64 - } 65 - 66 - return ( 67 - <div className="actions-page"> 68 - <Header agent={agent} onLogout={handleLogout} /> 69 - 70 - <div className="actions-container"> 71 - <div className="actions-list"> 72 - <button 73 - className="action-item" 74 - onClick={() => navigate('/migration')} 75 - > 76 - <div className="action-icon"> 77 - <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> 78 - <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" /> 79 - </svg> 80 - </div> 81 - <div className="action-content"> 82 - <div className="action-title">Migrate account</div> 83 - <div className="action-subtitle">Move your account to a new data server</div> 84 - </div> 85 - </button> 86 - 87 - <button 88 - className="action-item" 89 - onClick={() => navigate('/recovery-key')} 90 - > 91 - <div className="action-icon"> 92 - <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> 93 - <rect x="3" y="11" width="18" height="11" rx="2" ry="2" /> 94 - <path d="M7 11V7a5 5 0 0 1 10 0v4" /> 95 - </svg> 96 - </div> 97 - <div className="action-content"> 98 - <div className="action-title">Add recovery key</div> 99 - <div className="action-subtitle">Create a new recovery key for your account</div> 100 - </div> 101 - </button> 102 - </div> 103 - 104 - <details className="user-info-section"> 105 - <summary className="user-info-summary">User Information</summary> 106 - <div className="user-info-content"> 107 - <section> 108 - <h2>Account Details</h2> 109 - <dl> 110 - <dt>DID</dt> 111 - <dd>{agent.session?.did || 'N/A'}</dd> 112 - <dt>Handle</dt> 113 - <dd>@{agent.session?.handle || 'N/A'}</dd> 114 - <dt>PDS</dt> 115 - <dd>{agent.serviceUrl.toString() || 'N/A'}</dd> 116 - </dl> 117 - </section> 118 - 119 - <section> 120 - <h2>DID Document</h2> 121 - <pre className="did-document"> 122 - <code>{didDoc}</code> 123 - </pre> 124 - </section> 125 - </div> 126 - </details> 127 - </div> 128 - <Footer /> 129 - </div> 130 - ); 131 - }
···
+24
src/components/common/Footer.tsx
···
··· 1 + export default function Footer() { 2 + return ( 3 + <footer className="footer"> 4 + Made by{' '} 5 + <a 6 + href="https://bsky.app/profile/did:plc:5szlrh3xkfxxsuu4mo6oe6h7" 7 + target="_blank" 8 + rel="noopener noreferrer" 9 + className="footer-link" 10 + > 11 + <strong>@noob.quest</strong> 12 + </a>{' '} 13 + with hate ๐Ÿ’” | 14 + <a 15 + href="https://tangled.sh/@noob.quest/atproto-migrator/" 16 + target="_blank" 17 + rel="noopener noreferrer" 18 + className="footer-link" 19 + > 20 + <strong> source code</strong> 21 + </a> 22 + </footer> 23 + ); 24 + }
+50
src/components/common/Header.tsx
···
··· 1 + import { useEffect } from 'react'; 2 + import { AtpAgent } from '@atproto/api'; 3 + import { useAvatar } from '../../contexts/AvatarContext'; 4 + 5 + interface HeaderProps { 6 + agent: AtpAgent; 7 + onLogout: () => void; 8 + } 9 + 10 + export default function Header({ agent, onLogout }: HeaderProps) { 11 + const { avatarUrl, setAvatarUrl } = useAvatar(); 12 + 13 + useEffect(() => { 14 + const fetchProfile = async () => { 15 + // Only fetch if we don't already have an avatar 16 + if (!avatarUrl) { 17 + try { 18 + const profile = await agent.getProfile({ actor: agent.session?.handle || '' }); 19 + if (profile.data.avatar) { 20 + setAvatarUrl(profile.data.avatar); 21 + } 22 + } catch (err) { 23 + console.error('Error fetching profile avatar:', err); 24 + setAvatarUrl('https://placehold.co/400x400'); 25 + } 26 + } 27 + }; 28 + 29 + fetchProfile(); 30 + }, [agent, avatarUrl, setAvatarUrl]); 31 + 32 + return ( 33 + <header className="app-header"> 34 + <h1 className="app-title">ATproto Migrator</h1> 35 + <div className="user-info"> 36 + {avatarUrl && ( 37 + <img 38 + src={avatarUrl} 39 + alt="Profile" 40 + className="user-avatar" 41 + /> 42 + )} 43 + <span className="user-handle" title={agent.session?.handle}>@{agent.session?.handle}</span> 44 + <button className="logout-button" onClick={onLogout}> 45 + Logout 46 + </button> 47 + </div> 48 + </header> 49 + ); 50 + }
-72
src/components/common/Migration.tsx
··· 1 - import { useNavigate } from 'react-router-dom'; 2 - import { AtpAgent } from '@atproto/api'; 3 - import Footer from '../layout/Footer'; 4 - import Header from '../layout/Header'; 5 - import '../../styles/App.css'; 6 - 7 - interface MigrationProps { 8 - agent: AtpAgent; 9 - onLogout: () => void; 10 - } 11 - 12 - export default function Migration({ agent, onLogout }: MigrationProps) { 13 - const navigate = useNavigate(); 14 - 15 - return ( 16 - <div className="actions-page"> 17 - <Header agent={agent} onLogout={onLogout} /> 18 - 19 - <div className="actions-container"> 20 - <div className="page-content"> 21 - <h2>Migrate your account</h2> 22 - <p>This tool allows you to migrate your account to a new Personal Data Server, a data storage service that hosts your account and all of its data.</p> 23 - <h3>What to expect</h3> 24 - <p>The migration process is <i>possible</i>, however it is not recommended if you are unsure about what you are doing, especially if you are migrating your primary account.</p> 25 - <p>You will need the following items to begin the migration process:</p> 26 - <ul> 27 - <li>A new PDS to migrate to</li> 28 - <li>An invite code from the new PDS (if required)</li> 29 - <li>A PLC operation token to confirm the migration</li> 30 - <li>A new password for your account <b>(Your password will not be stored by this tool.)</b></li> 31 - <li>If you are not using a custom domain, you will need a new handle as the default domain (such as alice.bsky.social or bob.example-pds.com) is non-transferable.</li> 32 - </ul> 33 - 34 - <div className="warning-section"> 35 - <h3>โš ๏ธ Read Before Continuing โš ๏ธ</h3> 36 - <ul> 37 - <li>If you are already on a third-party PDS, it must be able to send emails or you will not be able to get a PLC operation token without direct access to the server.</li> 38 - <li>Due to performance issues, the main Bluesky data servers do not allow for account data to be imported at this time. <b>You will not be able to migrate back to Bluesky servers.</b></li> 39 - <li>If your PDS goes down and you do not have access to a recovery key, you will be locked out of your account. <b>Bluesky developers will not be able to help you.</b></li> 40 - </ul> 41 - </div> 42 - 43 - <div className="docs-section"> 44 - <h3>Additional Resources</h3> 45 - <p>For the technically inclined, here are some additional resources for how the migration process works:</p> 46 - <ul> 47 - <li><a href="https://github.com/bluesky-social/pds/blob/main/ACCOUNT_MIGRATION.md" target="_blank" rel="noopener noreferrer">Detailed document on migration for PDS hosters</a></li> 48 - <li><a href="https://atproto.com/guides/account-migration" target="_blank" rel="noopener noreferrer">AT Protocol's developer documentation on account migration</a></li> 49 - <li><a href="https://whtwnd.com/bnewbold.net/3l5ii332pf32u">Guide to migrating an account using the command line</a></li> 50 - </ul> 51 - </div> 52 - 53 - <div className="button-container"> 54 - <button 55 - className="back-button" 56 - onClick={() => navigate('/actions')} 57 - > 58 - โ† Go back 59 - </button> 60 - <button 61 - className="continue-button" 62 - onClick={() => navigate('/migration/process')} 63 - > 64 - Continue โ†’ 65 - </button> 66 - </div> 67 - </div> 68 - </div> 69 - <Footer /> 70 - </div> 71 - ); 72 - }
···
-377
src/components/common/MigrationProcess.tsx
··· 1 - import { useNavigate } from 'react-router-dom'; 2 - import { useState, useEffect, useCallback } from 'react'; 3 - import { AtpAgent } from '@atproto/api'; 4 - import Footer from '../layout/Footer'; 5 - import Header from '../layout/Header'; 6 - import '../../styles/App.css'; 7 - 8 - interface MigrationProcessProps { 9 - agent: AtpAgent; 10 - onLogout: () => void; 11 - } 12 - 13 - interface PDSInfo { 14 - exists: boolean; 15 - requiresInvite: boolean; 16 - domain: string; 17 - availableUserDomains: string[]; 18 - } 19 - 20 - interface AccountDetails { 21 - handle: string; 22 - email: string; 23 - password: string; 24 - } 25 - 26 - export default function MigrationProcess({ agent, onLogout }: MigrationProcessProps) { 27 - const navigate = useNavigate(); 28 - const [pds, setPds] = useState(''); 29 - const [pdsInfo, setPdsInfo] = useState<PDSInfo | null>(null); 30 - const [isValidating, setIsValidating] = useState(false); 31 - const [error, setError] = useState(''); 32 - const [inviteCode, setInviteCode] = useState(''); 33 - const [isInviteValid, setIsInviteValid] = useState(false); 34 - const [showAccountForm, setShowAccountForm] = useState(false); 35 - const [accountDetails, setAccountDetails] = useState<AccountDetails>({ 36 - handle: '', 37 - email: '', 38 - password: '' 39 - }); 40 - const [isCustomHandle, setIsCustomHandle] = useState(false); 41 - const [currentHandle, setCurrentHandle] = useState(''); 42 - 43 - // Add warning when trying to close or navigate away and clean up expired data 44 - useEffect(() => { 45 - const handleBeforeUnload = (e: BeforeUnloadEvent) => { 46 - // Clean up expired data 47 - const savedDetails = localStorage.getItem('migration_details'); 48 - if (savedDetails) { 49 - const { expiryTime } = JSON.parse(savedDetails); 50 - if (Date.now() >= expiryTime) { 51 - localStorage.removeItem('migration_details'); 52 - } 53 - } 54 - 55 - e.preventDefault(); 56 - e.returnValue = ''; 57 - return ''; 58 - }; 59 - 60 - window.addEventListener('beforeunload', handleBeforeUnload); 61 - 62 - return () => { 63 - window.removeEventListener('beforeunload', handleBeforeUnload); 64 - // Clean up expired data when component unmounts 65 - const savedDetails = localStorage.getItem('migration_details'); 66 - if (savedDetails) { 67 - const { expiryTime } = JSON.parse(savedDetails); 68 - if (Date.now() >= expiryTime) { 69 - localStorage.removeItem('migration_details'); 70 - } 71 - } 72 - }; 73 - }, []); 74 - 75 - // Get current user's handle and check if it's a default handle 76 - useEffect(() => { 77 - const checkCurrentHandle = async () => { 78 - try { 79 - const session = agent.session; 80 - if (session?.handle) { 81 - setCurrentHandle(session.handle); 82 - } 83 - } catch (err) { 84 - console.error('Failed to get current handle:', err); 85 - } 86 - }; 87 - checkCurrentHandle(); 88 - }, [agent]); 89 - 90 - // Debounced PDS validation 91 - const validatePDS = useCallback(async (pdsUrl: string) => { 92 - if (!pdsUrl) { 93 - setPdsInfo(null); 94 - setError(''); 95 - return; 96 - } 97 - 98 - setIsValidating(true); 99 - setError(''); 100 - 101 - try { 102 - // Ensure the URL has the correct format 103 - if (!pdsUrl.startsWith('http://') && !pdsUrl.startsWith('https://')) { 104 - pdsUrl = 'https://' + pdsUrl; 105 - } 106 - 107 - // Check if the PDS is a Bluesky PDS 108 - const hostname = new URL(pdsUrl).hostname; 109 - if (hostname === 'bsky.social' || hostname === 'bsky.app' || hostname.endsWith('bsky.network')) { 110 - setPdsInfo({ 111 - exists: false, 112 - requiresInvite: false, 113 - domain: hostname, 114 - availableUserDomains: [] 115 - }); 116 - setError('Bluesky currently does not support migrating accounts to their data servers.'); 117 - return; 118 - } 119 - 120 - // Create a temporary agent to check the PDS 121 - const tempAgent = new AtpAgent({ service: pdsUrl }); 122 - 123 - try { 124 - // Try to get the server info 125 - const info = await tempAgent.api.com.atproto.server.describeServer(); 126 - const domain = new URL(pdsUrl).hostname; 127 - 128 - setPdsInfo({ 129 - exists: true, 130 - requiresInvite: info.data.inviteCodeRequired || false, 131 - domain, 132 - availableUserDomains: info.data.availableUserDomains || [] 133 - }); 134 - } catch (err) { 135 - setPdsInfo({ 136 - exists: false, 137 - requiresInvite: false, 138 - domain: '', 139 - availableUserDomains: [] 140 - }); 141 - setError('Could not connect to the specified PDS. Please check the URL and try again.'); 142 - } 143 - } catch (err) { 144 - setError('Invalid PDS URL format. Please enter a valid URL.'); 145 - } finally { 146 - setIsValidating(false); 147 - } 148 - }, []); 149 - 150 - // Debounce the validation 151 - useEffect(() => { 152 - const timeoutId = setTimeout(() => { 153 - if (pds) { 154 - validatePDS(pds); 155 - } 156 - }, 500); 157 - 158 - return () => clearTimeout(timeoutId); 159 - }, [pds, validatePDS]); 160 - 161 - // Validate invite code when it changes 162 - useEffect(() => { 163 - if (pdsInfo?.requiresInvite && inviteCode) { 164 - const inviteRegex = /^bsky-noob-quest-[a-zA-Z0-9]{5}-[a-zA-Z0-9]{5}$/; 165 - setIsInviteValid(inviteRegex.test(inviteCode)); 166 - } 167 - }, [inviteCode, pdsInfo]); 168 - 169 - // Check if handle is custom 170 - useEffect(() => { 171 - if (accountDetails.handle && pdsInfo?.availableUserDomains?.length) { 172 - const defaultDomain = pdsInfo.availableUserDomains[0]; 173 - const handleRegex = new RegExp(`^[a-zA-Z0-9._-]+@${defaultDomain}$`); 174 - const isDefaultHandle = handleRegex.test(accountDetails.handle); 175 - setIsCustomHandle(!isDefaultHandle); 176 - } 177 - }, [accountDetails.handle, pdsInfo]); 178 - 179 - // Auto-scroll to latest step 180 - useEffect(() => { 181 - const formSection = document.querySelector('.form-section:not(.completed)'); 182 - if (formSection) { 183 - formSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); 184 - } 185 - }, [showAccountForm]); 186 - 187 - // Check if current handle is default 188 - const isCurrentHandleDefault = useCallback(() => { 189 - if (!currentHandle || !pdsInfo?.availableUserDomains?.length) return false; 190 - 191 - // If migrating from Bluesky PDS (bsky.network), check if handle is from bsky.social 192 - if (agent.serviceUrl.host.endsWith('.bsky.network')) { 193 - return currentHandle.endsWith('.bsky.social'); 194 - } 195 - 196 - // For third-party PDS, check if handle ends with any of the available user domains 197 - return pdsInfo.availableUserDomains.some(domain => 198 - currentHandle.endsWith(`${domain}`) 199 - ); 200 - }, [currentHandle, pdsInfo]); 201 - 202 - const handlePdsBlur = () => { 203 - if (pds) { 204 - validatePDS(pds); 205 - } 206 - }; 207 - 208 - const handleContinue = () => { 209 - if (pdsInfo?.exists && (!pdsInfo.requiresInvite || isInviteValid)) { 210 - setShowAccountForm(true); 211 - } 212 - }; 213 - 214 - const handleStartMigration = () => { 215 - // Clean up any existing expired data first 216 - const savedDetails = localStorage.getItem('migration_details'); 217 - if (savedDetails) { 218 - const { expiryTime } = JSON.parse(savedDetails); 219 - if (Date.now() >= expiryTime) { 220 - localStorage.removeItem('migration_details'); 221 - } 222 - } 223 - 224 - // Save account details to localStorage with 30-minute expiry 225 - const expiryTime = Date.now() + (30 * 60 * 1000); // 30 minutes in milliseconds 226 - const migrationDetails = { 227 - pds: pds, 228 - inviteCode: inviteCode || null, 229 - handle: accountDetails.handle + (pdsInfo?.availableUserDomains?.[0] ? `${pdsInfo.availableUserDomains[0]}` : ''), 230 - email: accountDetails.email, 231 - password: accountDetails.password, 232 - expiryTime: expiryTime 233 - }; 234 - localStorage.setItem('migration_details', JSON.stringify(migrationDetails)); 235 - 236 - // TODO: Implement migration 237 - }; 238 - 239 - return ( 240 - <div className="actions-page"> 241 - <Header agent={agent} onLogout={onLogout} /> 242 - 243 - <div className="actions-container"> 244 - <div className="page-content"> 245 - <h2>Migrate your account</h2> 246 - 247 - <div className={`form-section ${showAccountForm ? 'completed' : ''}`}> 248 - <h3>Select your new PDS</h3> 249 - <div className="form-group"> 250 - <label htmlFor="pds-input">Personal Data Server (PDS)</label> 251 - <input 252 - id="pds-input" 253 - type="text" 254 - className="form-input" 255 - placeholder="Example: example-pds.com" 256 - value={pds} 257 - onChange={(e) => setPds(e.target.value)} 258 - onBlur={handlePdsBlur} 259 - disabled={isValidating || showAccountForm} 260 - /> 261 - {isValidating && ( 262 - <div className="loading-message">Checking PDS availability...</div> 263 - )} 264 - {error && ( 265 - <div className="error-message">{error}</div> 266 - )} 267 - {pdsInfo?.exists && !pdsInfo.requiresInvite && ( 268 - <div className="success-message">โœ“ This PDS does not require an invite code</div> 269 - )} 270 - </div> 271 - 272 - {pdsInfo?.exists && pdsInfo.requiresInvite && ( 273 - <div className="form-group"> 274 - <label htmlFor="invite-code">Invite Code</label> 275 - <input 276 - id="invite-code" 277 - type="text" 278 - className="form-input" 279 - placeholder="Example: bsky-noob-quest-abcde-12345" 280 - value={inviteCode} 281 - onChange={(e) => setInviteCode(e.target.value)} 282 - disabled={showAccountForm} 283 - /> 284 - </div> 285 - )} 286 - 287 - {!showAccountForm && ( 288 - <div className="button-container"> 289 - <button 290 - className="back-button" 291 - onClick={() => navigate('/migration')} 292 - > 293 - โ† Go back 294 - </button> 295 - {pdsInfo?.exists && (!pdsInfo.requiresInvite || isInviteValid) && ( 296 - <button 297 - className="continue-button" 298 - onClick={handleContinue} 299 - > 300 - Continue โ†’ 301 - </button> 302 - )} 303 - </div> 304 - )} 305 - </div> 306 - 307 - {showAccountForm && ( 308 - <div className="form-section"> 309 - <h3>New account details</h3> 310 - <div className="form-group"> 311 - <label htmlFor="handle-input">Handle</label> 312 - <div className="handle-input-container"> 313 - <input 314 - id="handle-input" 315 - type="text" 316 - className="form-input" 317 - placeholder="username" 318 - value={accountDetails.handle} 319 - onChange={(e) => setAccountDetails(prev => ({ ...prev, handle: e.target.value }))} 320 - /> 321 - {pdsInfo?.availableUserDomains?.[0] && ( 322 - <span className="handle-domain">{pdsInfo.availableUserDomains[0]}</span> 323 - )} 324 - </div> 325 - {isCustomHandle && !isCurrentHandleDefault() && ( 326 - <div className="info-message"> 327 - During the migration, you'll be assigned a temporary handle. After the migration is completed, we will assign your custom handle automatically. 328 - </div> 329 - )} 330 - </div> 331 - 332 - <div className="form-group"> 333 - <label htmlFor="email-input">Email</label> 334 - <input 335 - id="email-input" 336 - type="email" 337 - className="form-input" 338 - placeholder="Your email address" 339 - value={accountDetails.email} 340 - onChange={(e) => setAccountDetails(prev => ({ ...prev, email: e.target.value }))} 341 - /> 342 - </div> 343 - 344 - <div className="form-group"> 345 - <label htmlFor="password-input">Password</label> 346 - <input 347 - id="password-input" 348 - type="password" 349 - className="form-input" 350 - placeholder="Your new password" 351 - value={accountDetails.password} 352 - onChange={(e) => setAccountDetails(prev => ({ ...prev, password: e.target.value }))} 353 - /> 354 - </div> 355 - <small>We recommend using a different password for your new account. Save all of the above details somewhere before continuing.</small> 356 - <div className="button-container"> 357 - <button 358 - className="back-button" 359 - onClick={() => setShowAccountForm(false)} 360 - > 361 - โ† Go back 362 - </button> 363 - <button 364 - className="continue-button" 365 - onClick={handleStartMigration} 366 - > 367 - Continue โ†’ 368 - </button> 369 - </div> 370 - </div> 371 - )} 372 - </div> 373 - </div> 374 - <Footer /> 375 - </div> 376 - ); 377 - }
···
-1
src/components/common/NetworkWarning.tsx
··· 1 import { useNetwork } from '../../contexts/NetworkContext'; 2 - import '../../styles/App.css'; 3 4 export default function NetworkWarning() { 5 const { isOnline } = useNetwork();
··· 1 import { useNetwork } from '../../contexts/NetworkContext'; 2 3 export default function NetworkWarning() { 4 const { isOnline } = useNetwork();
-67
src/components/common/RecoveryKey.tsx
··· 1 - import { useNavigate } from 'react-router-dom'; 2 - import { AtpAgent } from '@atproto/api'; 3 - import Footer from '../layout/Footer'; 4 - import Header from '../layout/Header'; 5 - import '../../styles/App.css'; 6 - 7 - interface RecoveryKeyProps { 8 - agent: AtpAgent; 9 - onLogout: () => void; 10 - } 11 - 12 - export default function RecoveryKey({ agent, onLogout }: RecoveryKeyProps) { 13 - const navigate = useNavigate(); 14 - 15 - return ( 16 - <div className="actions-page"> 17 - <Header agent={agent} onLogout={onLogout} /> 18 - 19 - <div className="actions-container"> 20 - <div className="page-content"> 21 - <h2>Add a recovery key</h2> 22 - <p>A recovery key (known as a <b>rotation key</b> in the AT Protocol) is a cryptographic key associated with your account that allows you to modify your account's core identity.</p> 23 - 24 - <h3>How rotation keys work</h3> 25 - <p>In the AT Protocol, your account is identified using a DID (<b>Decentralized Identifier</b>), with most accounts on the protocol using a variant of it developed specifically for the protocol. The account's core information (such as your handle and data server on the network) is stored in the account's DID document.</p> 26 - <p>To change this document, you use a rotation key to confirm that you are the owner of the account and that you are authorized to make the changes. For example, when changing your handle, your data server (also known as a PDS) will use its own rotation key to change it without asking you to manually sign the operation.</p> 27 - <h3>Why should I add another key?</h3> 28 - <p>Adding a rotation key allows you to regain control of your account if it is compromised. It also allows you to move your account to a new data server, even if the current server is down.</p> 29 - <div className="warning-section"> 30 - <h3>โš ๏ธ Read Before Continuing โš ๏ธ</h3> 31 - <ul> 32 - <li>You will need a PLC operation token to add a recovery key. Tokens are sent to the email address associated with your account.</li> 33 - <li>While we do generate a key for you, we will not store it. Please save it in a secure location.</li> 34 - <li>Keep your recovery key private. Anyone with access to it could potentially take control of your account.</li> 35 - <li>If you're using a third-party PDS, it must be able to send emails or you will not be able to use this tool to add a recovery key.</li> 36 - </ul> 37 - </div> 38 - <div className="docs-section"> 39 - <h3>Additional Resources</h3> 40 - <p>For the technically inclined, here are some additional resources for how rotation keys work:</p> 41 - <ul> 42 - <li><a href="https://atproto.com/guides/identity" target="_blank" rel="noopener noreferrer">AT Protocol's developer documentation on identity</a></li> 43 - <li><a href="https://whtwnd.com/did:plc:xz3euvkhf44iadavovbsmqoo/3laimapx6ks2b" target="_blank" rel="noopener noreferrer">Guide to adding a recovery key using the command line</a></li> 44 - <li><a href="https://whtwnd.com/did:plc:44ybard66vv44zksje25o7dz/3lj7jmt2ct72r" target="_blank" rel="noopener noreferrer">More in-depth guide to adding a recovery key</a></li> 45 - </ul> 46 - </div> 47 - 48 - <div className="button-container"> 49 - <button 50 - className="back-button" 51 - onClick={() => navigate('/actions')} 52 - > 53 - โ† Go back 54 - </button> 55 - <button 56 - className="continue-button" 57 - onClick={() => navigate('/recovery-key/process')} 58 - > 59 - Continue โ†’ 60 - </button> 61 - </div> 62 - </div> 63 - </div> 64 - <Footer /> 65 - </div> 66 - ); 67 - }
···
-53
src/components/common/RecoveryKeyProcess.tsx
··· 1 - import { useNavigate } from 'react-router-dom'; 2 - import { useEffect } from 'react'; 3 - import { AtpAgent } from '@atproto/api'; 4 - import Footer from '../layout/Footer'; 5 - import Header from '../layout/Header'; 6 - import '../../styles/App.css'; 7 - 8 - interface RecoveryKeyProcessProps { 9 - agent: AtpAgent; 10 - onLogout: () => void; 11 - } 12 - 13 - export default function RecoveryKeyProcess({ agent, onLogout }: RecoveryKeyProcessProps) { 14 - const navigate = useNavigate(); 15 - 16 - // Add warning when trying to close or navigate away 17 - useEffect(() => { 18 - const handleBeforeUnload = (e: BeforeUnloadEvent) => { 19 - e.preventDefault(); 20 - e.returnValue = ''; 21 - return ''; 22 - }; 23 - 24 - window.addEventListener('beforeunload', handleBeforeUnload); 25 - 26 - return () => { 27 - window.removeEventListener('beforeunload', handleBeforeUnload); 28 - }; 29 - }, []); 30 - 31 - return ( 32 - <div className="actions-page"> 33 - <Header agent={agent} onLogout={onLogout} /> 34 - 35 - <div className="actions-container"> 36 - <div className="page-content"> 37 - <h2>Add Recovery Key</h2> 38 - <p>This page will guide you through the process of adding a recovery key to your account.</p> 39 - 40 - <div className="button-container"> 41 - <button 42 - className="back-button" 43 - onClick={() => navigate('/recovery-key')} 44 - > 45 - โ† Go back 46 - </button> 47 - </div> 48 - </div> 49 - </div> 50 - <Footer /> 51 - </div> 52 - ); 53 - }
···
-26
src/components/layout/Footer.tsx
··· 1 - import '../../styles/App.css'; 2 - 3 - export default function Footer() { 4 - return ( 5 - <footer className="footer"> 6 - Made by{' '} 7 - <a 8 - href="https://bsky.app/profile/did:plc:5szlrh3xkfxxsuu4mo6oe6h7" 9 - target="_blank" 10 - rel="noopener noreferrer" 11 - className="footer-link" 12 - > 13 - <strong>@noob.quest</strong> 14 - </a>{' '} 15 - with hate ๐Ÿ’” | 16 - <a 17 - href="https://tangled.sh/@noob.quest/atproto-migrator/" 18 - target="_blank" 19 - rel="noopener noreferrer" 20 - className="footer-link" 21 - > 22 - <strong> source code</strong> 23 - </a> 24 - </footer> 25 - ); 26 - }
···
-50
src/components/layout/Header.tsx
··· 1 - import { useEffect } from 'react'; 2 - import { AtpAgent } from '@atproto/api'; 3 - import { useAvatar } from '../../contexts/AvatarContext'; 4 - import '../../styles/App.css'; 5 - 6 - interface HeaderProps { 7 - agent: AtpAgent; 8 - onLogout: () => void; 9 - } 10 - 11 - export default function Header({ agent, onLogout }: HeaderProps) { 12 - const { avatarUrl, setAvatarUrl } = useAvatar(); 13 - 14 - useEffect(() => { 15 - const fetchProfile = async () => { 16 - // Only fetch if we don't already have an avatar 17 - if (!avatarUrl) { 18 - try { 19 - const profile = await agent.getProfile({ actor: agent.session?.handle || '' }); 20 - if (profile.data.avatar) { 21 - setAvatarUrl(profile.data.avatar); 22 - } 23 - } catch (err) { 24 - console.error('Error fetching profile:', err); 25 - } 26 - } 27 - }; 28 - 29 - fetchProfile(); 30 - }, [agent, avatarUrl, setAvatarUrl]); 31 - 32 - return ( 33 - <header className="app-header"> 34 - <h1 className="app-title">ATproto Migrator</h1> 35 - <div className="user-info"> 36 - {avatarUrl && ( 37 - <img 38 - src={avatarUrl} 39 - alt="Profile" 40 - className="user-avatar" 41 - /> 42 - )} 43 - <span className="user-handle" title={agent.session?.handle}>{agent.session?.handle}</span> 44 - <button className="logout-button" onClick={onLogout}> 45 - Logout 46 - </button> 47 - </div> 48 - </header> 49 - ); 50 - }
···
+206
src/components/migration/accountDetailsForm.tsx
···
··· 1 + import { useState, useEffect, useCallback } from 'react'; 2 + import { ServerDescription } from '../../lib/migration/serverDescription'; 3 + import { validateHandle } from '../../lib/migration/accountDetailsValidation'; 4 + 5 + interface AccountDetailsFormProps { 6 + currentHandle: string; 7 + pds: string; 8 + inviteCode: string; 9 + serverDescription: ServerDescription; 10 + newServerDescription: ServerDescription; 11 + onBack: () => void; 12 + onSubmit: (handle: string, email: string, password: string) => void; 13 + } 14 + 15 + export default function AccountDetailsForm({ 16 + currentHandle, 17 + serverDescription, 18 + newServerDescription, 19 + onBack, 20 + onSubmit 21 + }: AccountDetailsFormProps) { 22 + const [handle, setHandle] = useState(''); 23 + const [email, setEmail] = useState(''); 24 + const [password, setPassword] = useState(''); 25 + const [isUsingDefaultDomain, setIsUsingDefaultDomain] = useState(false); 26 + const [emailError, setEmailError] = useState(''); 27 + const [passwordError, setPasswordError] = useState(''); 28 + const [handleError, setHandleError] = useState(''); 29 + 30 + useEffect(() => { 31 + // Check if current handle is using a default domain 32 + const availableDomains = serverDescription.getAvailableUserDomains(); 33 + const domainNames = Object.values(availableDomains); 34 + const { isUsingDefaultDomain, customHandle } = validateHandle(currentHandle, domainNames); 35 + 36 + 37 + setIsUsingDefaultDomain(isUsingDefaultDomain); 38 + if (isUsingDefaultDomain) { 39 + // Set initial handle value from current handle 40 + setHandle(customHandle); 41 + } else { 42 + setHandle(customHandle); 43 + } 44 + }, [currentHandle, serverDescription]); 45 + 46 + const validateEmail = (email: string) => { 47 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 48 + return emailRegex.test(email); 49 + }; 50 + 51 + const validatePassword = (password: string) => { 52 + return password.length >= 8; 53 + }; 54 + 55 + const validateHandleInput = (handle: string) => { 56 + if (!isUsingDefaultDomain) { 57 + return ''; 58 + } 59 + 60 + if (handle.length < 3) { 61 + return 'Handle must be at least 3 characters long'; 62 + } 63 + if (!/^[a-zA-Z0-9-]+$/.test(handle)) { 64 + return 'Handle can only contain letters, numbers, and hyphens'; 65 + } 66 + return ''; 67 + }; 68 + 69 + // Debounced validation functions 70 + const debouncedValidateHandle = useCallback((value: string) => { 71 + const timeoutId = setTimeout(() => { 72 + setHandleError(validateHandleInput(value)); 73 + }, 500); 74 + return () => clearTimeout(timeoutId); 75 + }, []); 76 + 77 + const debouncedValidateEmail = useCallback((value: string) => { 78 + const timeoutId = setTimeout(() => { 79 + setEmailError(validateEmail(value) ? '' : 'Please enter a valid email address'); 80 + }, 500); 81 + return () => clearTimeout(timeoutId); 82 + }, []); 83 + 84 + const debouncedValidatePassword = useCallback((value: string) => { 85 + const timeoutId = setTimeout(() => { 86 + setPasswordError(validatePassword(value) ? '' : 'Password must be at least 8 characters long'); 87 + }, 500); 88 + return () => clearTimeout(timeoutId); 89 + }, []); 90 + 91 + const validateAllFields = () => { 92 + // Run all validations immediately 93 + const handleValidationError = validateHandleInput(handle); 94 + const emailValidationError = !validateEmail(email) ? 'Please enter a valid email address' : ''; 95 + const passwordValidationError = !validatePassword(password) ? 'Password must be at least 8 characters long' : ''; 96 + 97 + // Update all error states 98 + setHandleError(handleValidationError); 99 + setEmailError(emailValidationError); 100 + setPasswordError(passwordValidationError); 101 + 102 + // Return true if all validations pass 103 + return !handleValidationError && !emailValidationError && !passwordValidationError; 104 + }; 105 + 106 + const handleSubmit = (e: React.FormEvent) => { 107 + e.preventDefault(); 108 + 109 + // Validate all fields and only proceed if all validations pass 110 + if (validateAllFields()) { 111 + onSubmit(handle, email, password); 112 + } 113 + }; 114 + 115 + // Get the first available domain from the new server description 116 + const newAvailableDomains = newServerDescription.getAvailableUserDomains(); 117 + const newFirstAvailableDomain = Object.values(newAvailableDomains)[0]; 118 + 119 + return ( 120 + <form onSubmit={handleSubmit}> 121 + <h3>Create your new account's credentials</h3> 122 + 123 + {isUsingDefaultDomain && ( 124 + <div> 125 + <div className="info-message"> 126 + <h3>Default handle detected!</h3> 127 + We have detected that your current handle is <strong>@{currentHandle}</strong>, which is a default handle provided by your data server. Because of this, we are unable to migrate your current handle to your new data server. We have automatically filled in your previous handle for you, however you can change it to something different if you wish. 128 + </div> 129 + 130 + <div className="form-group"> 131 + <label htmlFor="handle">Handle</label> 132 + <div className="handle-input-container"> 133 + <input 134 + type="text" 135 + id="handle" 136 + value={handle} 137 + onChange={(e) => { 138 + const newValue = e.target.value; 139 + setHandle(newValue); 140 + debouncedValidateHandle(newValue); 141 + }} 142 + placeholder="alice" 143 + className={`form-input ${handleError ? 'error' : ''}`} 144 + /> 145 + <span className="handle-domain">{newFirstAvailableDomain}</span> 146 + </div> 147 + {handleError && <div className="error-message">{handleError}</div>} 148 + </div> 149 + </div> 150 + )} 151 + 152 + <div className="form-group"> 153 + <label htmlFor="email">Email</label> 154 + <input 155 + type="text" 156 + id="email" 157 + value={email} 158 + onChange={(e) => { 159 + const newValue = e.target.value; 160 + setEmail(newValue); 161 + debouncedValidateEmail(newValue); 162 + }} 163 + placeholder="popbob@example.com" 164 + required 165 + className={`form-input ${emailError ? 'error' : ''}`} 166 + /> 167 + {emailError && <div className="error-message">{emailError}</div>} 168 + </div> 169 + 170 + <div className="form-group"> 171 + <label htmlFor="password">Password</label> 172 + <input 173 + type="password" 174 + id="password" 175 + value={password} 176 + onChange={(e) => { 177 + const newValue = e.target.value; 178 + setPassword(newValue); 179 + debouncedValidatePassword(newValue); 180 + }} 181 + placeholder="hunter2" 182 + required 183 + className={`form-input ${passwordError ? 'error' : ''}`} 184 + /> 185 + {passwordError && <div className="error-message">{passwordError}</div>} 186 + </div> 187 + <small>We recommend using a new, unique password for your new account.</small> 188 + 189 + <div className="button-container"> 190 + <button 191 + type="button" 192 + className="back-button" 193 + onClick={onBack} 194 + > 195 + โ† Go back 196 + </button> 197 + <button 198 + type="submit" 199 + className="continue-button" 200 + > 201 + Continue โ†’ 202 + </button> 203 + </div> 204 + </form> 205 + ); 206 + }
+165
src/components/migration/confirmationStep.tsx
···
··· 1 + import { useState } from 'react'; 2 + import '../../css/confirmation.css'; 3 + 4 + interface ConfirmationStepProps { 5 + handle: string; 6 + email: string; 7 + password: string; 8 + pds: string; 9 + onBack: () => void; 10 + onConfirm: () => void; 11 + currentHandle?: string; 12 + currentPds?: string; 13 + } 14 + 15 + export default function ConfirmationStep({ 16 + handle, 17 + email, 18 + password, 19 + pds, 20 + onBack, 21 + onConfirm, 22 + currentHandle = '', 23 + currentPds = '' 24 + }: ConfirmationStepProps) { 25 + const [showPassword, setShowPassword] = useState(false); 26 + const [showWarning, setShowWarning] = useState(false); 27 + const [pdsVerification, setPdsVerification] = useState(''); 28 + 29 + const handleConfirmClick = () => { 30 + setShowWarning(true); 31 + }; 32 + 33 + const handleWarningConfirm = () => { 34 + setShowWarning(false); 35 + setPdsVerification(''); 36 + onConfirm(); 37 + }; 38 + 39 + const isPdsVerified = pdsVerification.toLowerCase() === pds.toLowerCase(); 40 + 41 + return ( 42 + <div> 43 + <h3>Migration summary</h3> 44 + 45 + <div className="comparison-container"> 46 + <div className="comparison-side"> 47 + <h3>Before</h3> 48 + <div className="detail-group"> 49 + <label>Handle</label> 50 + <div>@{currentHandle}</div> 51 + </div> 52 + <div className="detail-group"> 53 + <label>PDS</label> 54 + <div>{currentPds}</div> 55 + </div> 56 + </div> 57 + 58 + <div className="comparison-arrow">โ†’</div> 59 + 60 + <div className="comparison-side"> 61 + <h3>After</h3> 62 + <div className="detail-group"> 63 + <label>Handle</label> 64 + <div>@{handle}</div> 65 + </div> 66 + <div className="detail-group"> 67 + <label>PDS</label> 68 + <div>{pds}</div> 69 + </div> 70 + </div> 71 + </div> 72 + 73 + <div className="detail-group"> 74 + <label>New Email</label> 75 + <div className="detail-value">{email}</div> 76 + </div> 77 + 78 + <div className="detail-group"> 79 + <label>New Password</label> 80 + <div className="password-container"> 81 + <div className="detail-value"> 82 + {showPassword ? ( 83 + password 84 + ) : ( 85 + <span className="hidden-password"> 86 + {'โ€ข'.repeat(password.length)} 87 + </span> 88 + )} 89 + </div> 90 + <button 91 + type="button" 92 + className="password-toggle" 93 + onClick={() => setShowPassword(!showPassword)} 94 + > 95 + {showPassword ? 'Hide' : 'Show'} 96 + </button> 97 + </div> 98 + </div> 99 + 100 + <div className="button-container"> 101 + <button 102 + type="button" 103 + className="back-button" 104 + onClick={onBack} 105 + > 106 + โ† Go back 107 + </button> 108 + <button 109 + type="button" 110 + className="confirm-button" 111 + onClick={handleConfirmClick} 112 + > 113 + Confirm Migration โ†’ 114 + </button> 115 + </div> 116 + 117 + {showWarning && ( 118 + <div className="warning-overlay"> 119 + <div className="warning-dialog"> 120 + <h3>Last chance to turn back!</h3> 121 + <div className="warning-content"> 122 + <p>This tool is currently in beta, as such:</p> 123 + <ul> 124 + <li>We do not guarantee that the migration will succeed</li> 125 + <li>While most errors are recoverable, for the moment we cannot help you recover from them and it will need to be continued manually using command line tools</li> 126 + <li>We claim no responsibility for any problems that may occur during the process</li> 127 + </ul> 128 + <p>If you understand the risks and want to proceed, please type your new PDS <b>({pds})</b> below:</p> 129 + <div className="pds-verification"> 130 + <input 131 + type="text" 132 + value={pdsVerification} 133 + onChange={(e) => setPdsVerification(e.target.value)} 134 + placeholder="Type your new PDS here" 135 + className={pdsVerification && !isPdsVerified ? 'error' : ''} 136 + /> 137 + </div> 138 + </div> 139 + <div className="warning-buttons"> 140 + <button 141 + type="button" 142 + className="back-button" 143 + onClick={handleWarningConfirm} 144 + disabled={!isPdsVerified} 145 + > 146 + Migrate! 147 + </button> 148 + <button 149 + type="button" 150 + className="confirm-button" 151 + onClick={() => { 152 + setShowWarning(false); 153 + setPdsVerification(''); 154 + }} 155 + > 156 + Cancel 157 + </button> 158 + 159 + </div> 160 + </div> 161 + </div> 162 + )} 163 + </div> 164 + ); 165 + }
+258
src/components/migration/pdsForm.tsx
···
··· 1 + import { useState, useCallback, useEffect } from 'react'; 2 + import { AtpAgent } from '@atproto/api'; 3 + import { validatePDS, getServerDescription, validateInviteCode } from '../../lib/migration/pdsValidation'; 4 + import { ServerDescription } from '../../lib/migration/serverDescription'; 5 + import { useDebounce } from '../../hooks/useDebounce'; 6 + 7 + interface PdsFormProps { 8 + agent: AtpAgent; 9 + onSubmit: (pds: string, inviteCode: string, serverDescription: ServerDescription) => void; 10 + onBack: () => void; 11 + } 12 + 13 + export default function PdsForm({ agent, onSubmit, onBack }: PdsFormProps) { 14 + const [pds, setPds] = useState(''); 15 + const [inviteCode, setInviteCode] = useState(''); 16 + const [pdsError, setPdsError] = useState<string | null>(null); 17 + const [inviteCodeError, setInviteCodeError] = useState<string | null>(null); 18 + const [isValidatingPds, setIsValidatingPds] = useState(false); 19 + const [isValidatingInviteCode, setIsValidatingInviteCode] = useState(false); 20 + const [isPdsValid, setIsPdsValid] = useState(false); 21 + const [isInviteCodeValid, setIsInviteCodeValid] = useState(false); 22 + const [serverDescription, setServerDescription] = useState<ServerDescription | null>(null); 23 + const [lastValidatedPds, setLastValidatedPds] = useState(''); 24 + const [isFormReady, setIsFormReady] = useState(false); 25 + 26 + useEffect(() => { 27 + const isInviteCodeSectionValid = !serverDescription?.isInviteCodeRequired() || 28 + Boolean(inviteCode && !inviteCodeError && !isValidatingInviteCode && isInviteCodeValid); 29 + 30 + setIsFormReady( 31 + Boolean(isPdsValid && 32 + !pdsError && 33 + !isValidatingPds && 34 + isInviteCodeSectionValid) 35 + ); 36 + }, [isPdsValid, pdsError, isValidatingPds, serverDescription, inviteCode, inviteCodeError, isValidatingInviteCode, isInviteCodeValid]); 37 + 38 + const validatePdsInput = useCallback(async (value: string) => { 39 + if (value === lastValidatedPds && isPdsValid && !pdsError) { 40 + return; 41 + } 42 + 43 + setIsValidatingPds(true); 44 + setIsPdsValid(false); 45 + setServerDescription(null); 46 + setPdsError(null); 47 + 48 + try { 49 + const validationResult = await validatePDS(value, agent); 50 + if (!validationResult.isValid) { 51 + console.error('PDS validation failed:', { 52 + pds: value, 53 + error: validationResult.error 54 + }); 55 + setPdsError(validationResult.error); 56 + setIsValidatingPds(false); 57 + return; 58 + } 59 + 60 + try { 61 + const description = await getServerDescription(value); 62 + setServerDescription(description); 63 + setPdsError(null); 64 + setIsPdsValid(true); 65 + setLastValidatedPds(value); 66 + } catch (e) { 67 + console.error('Error getting server description:', { 68 + pds: value, 69 + error: e instanceof Error ? { 70 + name: e.name, 71 + message: e.message, 72 + stack: e.stack 73 + } : e 74 + }); 75 + setPdsError('Failed to get server information'); 76 + } 77 + } catch (e) { 78 + console.error('Error validating PDS:', { 79 + pds: value, 80 + error: e instanceof Error ? { 81 + name: e.name, 82 + message: e.message, 83 + stack: e.stack 84 + } : e 85 + }); 86 + setPdsError('An unexpected error occurred'); 87 + } 88 + 89 + setIsValidatingPds(false); 90 + }, [agent, lastValidatedPds, isPdsValid, pdsError]); 91 + 92 + const validateInviteCodeInput = useCallback(async (pdsValue: string, inviteCodeValue: string) => { 93 + if (!pdsValue || !inviteCodeValue) { 94 + setIsInviteCodeValid(false); 95 + return; 96 + } 97 + 98 + setIsValidatingInviteCode(true); 99 + setInviteCodeError(null); 100 + setIsInviteCodeValid(false); 101 + 102 + try { 103 + const inviteCodeValidationResult = await validateInviteCode(pdsValue, inviteCodeValue); 104 + if (!inviteCodeValidationResult.isValid) { 105 + setInviteCodeError(inviteCodeValidationResult.error); 106 + setIsInviteCodeValid(false); 107 + } else { 108 + setIsInviteCodeValid(true); 109 + } 110 + } catch (e) { 111 + console.error('Error validating invite code:', { 112 + pds: pdsValue, 113 + inviteCode: inviteCodeValue, 114 + error: e instanceof Error ? { 115 + name: e.name, 116 + message: e.message, 117 + stack: e.stack 118 + } : e 119 + }); 120 + setInviteCodeError('Failed to validate invite code'); 121 + setIsInviteCodeValid(false); 122 + } 123 + 124 + setIsValidatingInviteCode(false); 125 + }, []); 126 + 127 + const debouncedValidatePds = useDebounce(validatePdsInput, 500); 128 + const debouncedValidateInviteCode = useDebounce(validateInviteCodeInput, 500); 129 + 130 + const handleSubmit = async (e: React.FormEvent) => { 131 + e.preventDefault(); 132 + if (!isFormReady) return; 133 + 134 + setIsValidatingPds(true); 135 + setIsValidatingInviteCode(true); 136 + 137 + try { 138 + await validatePdsInput(pds); 139 + 140 + if (serverDescription?.isInviteCodeRequired()) { 141 + await validateInviteCodeInput(pds, inviteCode); 142 + } 143 + 144 + if (!pdsError && !inviteCodeError) { 145 + onSubmit(pds, inviteCode, serverDescription!); 146 + } 147 + } catch (e) { 148 + console.error('Error during form submission:', { 149 + pds, 150 + inviteCode, 151 + error: e instanceof Error ? { 152 + name: e.name, 153 + message: e.message, 154 + stack: e.stack 155 + } : e 156 + }); 157 + setPdsError('An unexpected error occurred'); 158 + } 159 + 160 + setIsValidatingPds(false); 161 + setIsValidatingInviteCode(false); 162 + }; 163 + 164 + const handlePdsChange = (e: React.ChangeEvent<HTMLInputElement>) => { 165 + const value = e.target.value; 166 + setPds(value); 167 + setIsPdsValid(false); 168 + setServerDescription(null); 169 + setLastValidatedPds(''); 170 + setPdsError(null); 171 + debouncedValidatePds(value); 172 + }; 173 + 174 + const handleInviteCodeChange = (e: React.ChangeEvent<HTMLInputElement>) => { 175 + const value = e.target.value; 176 + setInviteCode(value); 177 + setInviteCodeError(null); 178 + setIsInviteCodeValid(false); 179 + if (pds) { 180 + debouncedValidateInviteCode(pds, value); 181 + } 182 + }; 183 + 184 + const handlePdsBlur = () => { 185 + validatePdsInput(pds); 186 + }; 187 + 188 + const handleInviteCodeBlur = () => { 189 + if (pds && inviteCode) { 190 + validateInviteCodeInput(pds, inviteCode); 191 + } 192 + }; 193 + 194 + return ( 195 + <form onSubmit={handleSubmit}> 196 + <h3>Find your new host</h3> 197 + <div className="form-group"> 198 + <label htmlFor="pds">Personal Data Server (PDS)</label> 199 + <input 200 + type="text" 201 + id="pds" 202 + value={pds} 203 + onChange={handlePdsChange} 204 + onBlur={handlePdsBlur} 205 + placeholder="Example: pds.example.com" 206 + required 207 + className={`form-input ${pdsError ? 'error' : ''} ${isPdsValid ? 'success' : ''}`} 208 + disabled={isValidatingPds} 209 + /> 210 + {(!isValidatingPds && pdsError && <div className="error-message">{pdsError}</div>) || 211 + (isValidatingPds && <div className="info-message" style={{ marginTop: '0.5rem' }}>Checking if the data server is valid...</div>) || 212 + (isPdsValid && <div className="success-message" style={{ marginTop: '0.5rem' }}>โœ“ This data server is alive!</div>)} 213 + 214 + {serverDescription?.isInviteCodeRequired() && ( 215 + <div className="form-group" style={{ marginTop: '1rem' }}> 216 + <label htmlFor="inviteCode">Invite Code</label> 217 + <input 218 + type="text" 219 + id="inviteCode" 220 + value={inviteCode} 221 + onChange={handleInviteCodeChange} 222 + onBlur={handleInviteCodeBlur} 223 + placeholder={`Example: ${pds.replace(/\./g, '-')}-XXXXX-XXXXX`} 224 + required 225 + className={`form-input ${inviteCodeError ? 'error' : ''} ${isInviteCodeValid ? 'success' : ''}`} 226 + disabled={isValidatingInviteCode} 227 + /> 228 + {(!isValidatingInviteCode && inviteCodeError && <div className="error-message">{inviteCodeError}</div>) || 229 + (isValidatingInviteCode && <div className="info-message" style={{ marginTop: '0.5rem' }}>Validating invite code...</div>) || 230 + (isInviteCodeValid && <div className="success-message" style={{ marginTop: '0.5rem' }}>โœ“ Invite code is valid</div>)} 231 + <div className="info-message" style={{ marginTop: '0.5rem' }}> 232 + This server requires an invite code to register. 233 + </div> 234 + </div> 235 + )} 236 + 237 + <div className="button-container"> 238 + <button 239 + type="button" 240 + className="back-button" 241 + onClick={onBack} 242 + disabled={isValidatingPds || isValidatingInviteCode} 243 + > 244 + โ† Go back 245 + </button> 246 + {isFormReady && ( 247 + <button 248 + type="submit" 249 + className="continue-button" 250 + > 251 + Continue โ†’ 252 + </button> 253 + )} 254 + </div> 255 + </div> 256 + </form> 257 + ); 258 + }
+158
src/css/actions.css
···
··· 1 + /* Actions page styles */ 2 + .actions-page { 3 + display: flex; 4 + flex-direction: column; 5 + min-height: 100vh; 6 + max-width: 800px; 7 + margin: 0 auto; 8 + padding: 0 20px; 9 + gap: 1rem; 10 + } 11 + 12 + .actions-container { 13 + padding: 0; 14 + max-width: 800px; 15 + width: 100%; 16 + margin: 0 auto; 17 + } 18 + 19 + .actions-list { 20 + display: flex; 21 + flex-direction: column; 22 + gap: 1rem; 23 + margin-top: 1rem; 24 + } 25 + 26 + .action-item { 27 + display: flex; 28 + align-items: center; 29 + gap: 1rem; 30 + padding: 1rem; 31 + background-color: var(--white); 32 + border-radius: 0.5rem; 33 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 34 + cursor: pointer; 35 + transition: transform 0.2s, box-shadow 0.2s; 36 + border: none; 37 + width: 100%; 38 + text-align: left; 39 + } 40 + 41 + .action-item:hover { 42 + transform: translateY(-2px); 43 + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 44 + } 45 + 46 + .action-icon { 47 + width: 2.5rem; 48 + height: 2.5rem; 49 + display: flex; 50 + align-items: center; 51 + justify-content: center; 52 + color: var(--primary-color); 53 + background-color: var(--bg-color); 54 + border-radius: 0.5rem; 55 + flex-shrink: 0; 56 + } 57 + 58 + .action-content { 59 + flex: 1; 60 + text-align: left; 61 + } 62 + 63 + .action-title { 64 + font-size: 1rem; 65 + font-weight: 600; 66 + color: var(--text-color); 67 + margin-bottom: 0.25rem; 68 + text-align: left; 69 + } 70 + 71 + .action-subtitle { 72 + font-size: 0.875rem; 73 + color: var(--text-light); 74 + text-align: left; 75 + } 76 + 77 + /* User info section */ 78 + .user-info-section { 79 + background-color: var(--white); 80 + border-radius: 0.5rem; 81 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 82 + overflow: hidden; 83 + margin-top: 1rem; 84 + } 85 + 86 + .user-info-summary { 87 + padding: 1rem; 88 + cursor: pointer; 89 + display: flex; 90 + align-items: center; 91 + justify-content: space-between; 92 + list-style: none; 93 + font-weight: 600; 94 + color: var(--text-color); 95 + } 96 + 97 + .user-info-summary::after { 98 + content: "โ–ผ"; 99 + color: var(--primary-color); 100 + transition: transform 0.2s; 101 + } 102 + 103 + .user-info-section[open] .user-info-summary::after { 104 + transform: rotate(180deg); 105 + } 106 + 107 + .user-info-content { 108 + padding: 1.5rem; 109 + border-top: 1px solid var(--border-color); 110 + } 111 + 112 + .user-info-content section { 113 + margin-bottom: 2rem; 114 + } 115 + 116 + .user-info-content section:last-child { 117 + margin-bottom: 0; 118 + } 119 + 120 + .user-info-content h2 { 121 + font-size: 1.125rem; 122 + font-weight: 600; 123 + color: var(--text-color); 124 + margin-bottom: 1rem; 125 + } 126 + 127 + .user-info-content dl { 128 + display: grid; 129 + grid-template-columns: max-content 1fr; 130 + gap: 0.75rem 1rem; 131 + margin: 0; 132 + } 133 + 134 + .user-info-content dt { 135 + font-weight: 500; 136 + color: var(--text-light); 137 + } 138 + 139 + .user-info-content dd { 140 + margin: 0; 141 + color: var(--text-color); 142 + word-break: break-all; 143 + } 144 + 145 + .did-document { 146 + background-color: var(--bg-color); 147 + padding: 1rem; 148 + border-radius: 0.375rem; 149 + overflow-x: auto; 150 + margin: 0; 151 + } 152 + 153 + .did-document code { 154 + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 155 + font-size: 0.875rem; 156 + color: var(--text-color); 157 + white-space: pre; 158 + }
+175
src/css/base.css
···
··· 1 + /* Base styles */ 2 + :root { 3 + --primary-color: #4338ca; 4 + --primary-hover: #4f46e5; 5 + --text-color: #1f2937; 6 + --text-light: #6b7280; 7 + --bg-color: #f3f4f6; 8 + --white: #ffffff; 9 + --error-color: #ef4444; 10 + --border-color: #e5e7eb; 11 + --input-bg: #ffffff; 12 + } 13 + 14 + @media (prefers-color-scheme: dark) { 15 + :root { 16 + --primary-color: #4f46e5; 17 + --primary-hover: #6366f1; 18 + --text-color: #f3f4f6; 19 + --text-light: #9ca3af; 20 + --bg-color: #111827; 21 + --white: #1f2937; 22 + --error-color: #f87171; 23 + --border-color: #374151; 24 + --input-bg: #1f2937; 25 + } 26 + } 27 + 28 + body { 29 + margin: 0; 30 + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; 31 + background-color: var(--bg-color); 32 + color: var(--text-color); 33 + min-height: 100vh; 34 + display: flex; 35 + flex-direction: column; 36 + } 37 + 38 + /* Common utility classes */ 39 + .page-content { 40 + background-color: var(--white); 41 + border-radius: 0.5rem; 42 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 43 + padding: 2rem; 44 + padding-top: 1rem; 45 + margin-top: 1rem; 46 + } 47 + 48 + .page-content h2 { 49 + font-size: 1.5rem; 50 + font-weight: 600; 51 + color: var(--text-color); 52 + margin-bottom: 1rem; 53 + } 54 + 55 + .page-content p { 56 + color: var(--text-color); 57 + margin-bottom: 1rem; 58 + } 59 + 60 + .page-content ul { 61 + list-style-type: disc; 62 + margin-left: 1.5rem; 63 + margin-bottom: 1.5rem; 64 + color: var(--text-color); 65 + } 66 + 67 + .page-content li { 68 + margin-bottom: 0.5rem; 69 + } 70 + 71 + .page-content h3 { 72 + font-size: 1.25rem; 73 + font-weight: 600; 74 + color: var(--text-color); 75 + margin: 1.5rem 0 1rem 0; 76 + } 77 + 78 + /* Warning section */ 79 + .warning-section { 80 + background-color: #fef3c7; 81 + border: 1px solid #fbbf24; 82 + border-radius: 0.5rem; 83 + padding: 1.5rem; 84 + margin: 1.5rem 0; 85 + } 86 + 87 + .warning-section h3 { 88 + color: #92400e; 89 + margin-top: 0; 90 + } 91 + 92 + .warning-section ul { 93 + margin-bottom: 0; 94 + padding-left: 0; 95 + } 96 + 97 + .warning-section li { 98 + color: #92400e; 99 + } 100 + 101 + .warning-section b { 102 + color: #78350f; 103 + } 104 + 105 + /* Docs section */ 106 + .docs-section { 107 + background-color: var(--bg-color); 108 + border-radius: 0.5rem; 109 + padding: 1.5rem; 110 + margin: 1.5rem 0; 111 + } 112 + 113 + .docs-section h3 { 114 + margin-top: 0; 115 + } 116 + 117 + .docs-section ul { 118 + margin-bottom: 0; 119 + padding-left: 0; 120 + } 121 + 122 + .docs-section a { 123 + color: var(--primary-color); 124 + text-decoration: none; 125 + transition: color 0.2s; 126 + display: inline-flex; 127 + align-items: center; 128 + gap: 0.5rem; 129 + } 130 + 131 + .docs-section a:hover { 132 + color: var(--primary-hover); 133 + } 134 + 135 + .docs-section a::after { 136 + content: "โ†’"; 137 + transition: transform 0.2s; 138 + } 139 + 140 + .docs-section a:hover::after { 141 + transform: translateX(4px); 142 + } 143 + 144 + /* Network warning */ 145 + .network-warning { 146 + position: fixed; 147 + top: 0; 148 + left: 0; 149 + right: 0; 150 + bottom: 0; 151 + background-color: rgba(0, 0, 0, 0.8); 152 + z-index: 1000; 153 + display: flex; 154 + align-items: center; 155 + justify-content: center; 156 + } 157 + 158 + .network-warning-content { 159 + max-width: 800px; 160 + padding: 2rem; 161 + display: flex; 162 + align-items: center; 163 + gap: 0.75rem; 164 + color: #92400e; 165 + font-size: 1.125rem; 166 + text-align: center; 167 + background-color: rgba(255, 255, 255, 0.9); 168 + border-radius: 0.5rem; 169 + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 170 + } 171 + 172 + .network-warning-icon { 173 + font-size: 1.5rem; 174 + flex-shrink: 0; 175 + }
+92
src/css/buttons.css
···
··· 1 + /* Button styles */ 2 + .submit-button { 3 + width: 100%; 4 + padding: 0.75rem; 5 + background-color: var(--primary-color); 6 + color: #ffffff; 7 + border: none; 8 + border-radius: 0.375rem; 9 + font-size: 0.875rem; 10 + font-weight: 500; 11 + cursor: pointer; 12 + transition: background-color 0.2s; 13 + margin-top: 1rem; 14 + } 15 + 16 + .submit-button:hover { 17 + background-color: var(--primary-hover); 18 + } 19 + 20 + .submit-button:disabled { 21 + background-color: var(--text-light); 22 + cursor: not-allowed; 23 + opacity: 0.8; 24 + } 25 + 26 + .back-button { 27 + background-color: #f3f4f6; 28 + color: #1f2937; 29 + border: 1px solid #e5e7eb; 30 + padding: 0.75rem 1.5rem; 31 + border-radius: 0.375rem; 32 + font-size: 0.875rem; 33 + font-weight: 500; 34 + cursor: pointer; 35 + transition: all 0.2s; 36 + display: inline-flex; 37 + align-items: center; 38 + gap: 0.5rem; 39 + } 40 + 41 + .back-button:hover { 42 + background-color: #e5e7eb; 43 + } 44 + 45 + @media (prefers-color-scheme: dark) { 46 + .back-button { 47 + background-color: #374151; 48 + color: #f3f4f6; 49 + border-color: #4b5563; 50 + } 51 + 52 + .back-button:hover { 53 + background-color: #4b5563; 54 + } 55 + } 56 + 57 + .continue-button { 58 + background-color: #312893; 59 + color: #ffffff; 60 + border: none; 61 + padding: 0.75rem 1.5rem; 62 + border-radius: 0.375rem; 63 + font-size: 0.875rem; 64 + font-weight: 500; 65 + cursor: pointer; 66 + transition: background-color 0.2s; 67 + display: inline-flex; 68 + align-items: center; 69 + gap: 0.5rem; 70 + margin-left: auto; 71 + } 72 + 73 + .continue-button:hover { 74 + background-color: #4f46e5; 75 + } 76 + 77 + @media (prefers-color-scheme: dark) { 78 + .continue-button { 79 + background-color: #4547b3; 80 + } 81 + 82 + .continue-button:hover { 83 + background-color: #5557de; 84 + } 85 + } 86 + 87 + .button-container { 88 + display: flex; 89 + justify-content: space-between; 90 + gap: 1rem; 91 + margin-top: 2rem; 92 + }
+216
src/css/confirmation.css
···
··· 1 + .comparison-container { 2 + display: flex; 3 + align-items: center; 4 + gap: 1rem; 5 + padding: 0 1.5rem 1.5rem 0; 6 + background-color: var(--white); 7 + border-radius: 0.5rem; 8 + flex-wrap: wrap; 9 + width: 100%; 10 + } 11 + 12 + .comparison-side { 13 + flex: 1; 14 + min-width: 200px; 15 + padding: 1rem; 16 + background-color: var(--bg-color); 17 + border-radius: 0.375rem; 18 + } 19 + 20 + .comparison-side h4 { 21 + margin: 0 0 0.75rem 0; 22 + color: var(--text-light); 23 + font-size: 0.875rem; 24 + font-weight: 500; 25 + } 26 + 27 + .comparison-arrow { 28 + display: flex; 29 + align-items: center; 30 + justify-content: center; 31 + width: 2.5rem; 32 + height: 2.5rem; 33 + background-color: var(--primary-color); 34 + border-radius: 50%; 35 + color: white; 36 + font-size: 1.25rem; 37 + } 38 + 39 + .detail-group { 40 + margin-bottom: 1rem; 41 + } 42 + 43 + .detail-group label { 44 + display: block; 45 + margin-bottom: 0.5rem; 46 + color: var(--text-light); 47 + font-size: 0.875rem; 48 + } 49 + 50 + .detail-value { 51 + padding: 0.75rem; 52 + background-color: var(--bg-color); 53 + border-radius: 0.375rem; 54 + font-size: 0.875rem; 55 + color: var(--text-color); 56 + } 57 + 58 + .password-container { 59 + position: relative; 60 + } 61 + 62 + .password-toggle { 63 + position: absolute; 64 + right: 0.75rem; 65 + top: 50%; 66 + transform: translateY(-50%); 67 + background: none; 68 + border: none; 69 + color: var(--text-light); 70 + cursor: pointer; 71 + padding: 0.25rem; 72 + font-size: 0.875rem; 73 + z-index: 1; 74 + } 75 + 76 + .password-toggle:hover { 77 + color: var(--text-color); 78 + } 79 + 80 + .hidden-password { 81 + letter-spacing: 0.25em; 82 + } 83 + 84 + .form-section.completed .button-container { 85 + display: none; 86 + } 87 + 88 + .confirm-button { 89 + background-color: #c72424; 90 + color: var(--text-color); 91 + border: none; 92 + padding: 0.75rem 1.5rem; 93 + border-radius: 0.375rem; 94 + font-size: 0.875rem; 95 + font-weight: 500; 96 + cursor: pointer; 97 + transition: background-color 0.2s; 98 + display: inline-flex; 99 + align-items: center; 100 + gap: 0.5rem; 101 + margin-left: auto; 102 + } 103 + 104 + .confirm-button:hover { 105 + background-color: #f32e2e; 106 + } 107 + 108 + @media (max-width: 707px) { 109 + .comparison-container { 110 + flex-direction: column; 111 + align-items: center; 112 + } 113 + 114 + .comparison-side { 115 + margin: 0 0 0 0; 116 + padding: 1rem 0 0 0; 117 + } 118 + 119 + /* UTTER WOKE NONSENSE */ 120 + .comparison-side h3, .comparison-side label, .comparison-side .detail-group div { 121 + padding: 0 1rem 0 1rem; 122 + } 123 + 124 + .comparison-arrow { 125 + transform: rotate(90deg); 126 + margin: 0.5rem 0; 127 + } 128 + } 129 + 130 + .warning-overlay { 131 + position: fixed; 132 + top: 0; 133 + left: 0; 134 + right: 0; 135 + bottom: 0; 136 + background-color: rgba(0, 0, 0, 0.5); 137 + display: flex; 138 + align-items: center; 139 + justify-content: center; 140 + z-index: 1000; 141 + } 142 + 143 + .warning-dialog { 144 + background-color: var(--white); 145 + border-radius: 0.5rem; 146 + padding: 1.5rem; 147 + max-width: 500px; 148 + width: 90%; 149 + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 150 + } 151 + 152 + .warning-dialog h3 { 153 + color: var(--error-color); 154 + margin: 0 0 1rem 0; 155 + font-size: 1.25rem; 156 + } 157 + 158 + .warning-content { 159 + margin-bottom: 1.5rem; 160 + } 161 + 162 + .warning-content p { 163 + margin: 0 0 1rem 0; 164 + color: var(--text-color); 165 + } 166 + 167 + .warning-content ul { 168 + margin: 0 0 1rem 0; 169 + padding-left: 1.5rem; 170 + } 171 + 172 + .warning-content li { 173 + margin-bottom: 0.5rem; 174 + color: var(--text-color); 175 + } 176 + 177 + .warning-buttons { 178 + display: flex; 179 + justify-content: flex-end; 180 + gap: 1rem; 181 + } 182 + 183 + .pds-verification { 184 + margin: 1rem 0; 185 + } 186 + 187 + .pds-verification input { 188 + width: 98%; 189 + padding: 0.75rem 0 0.75rem 0.5rem; 190 + border: 1px solid var(--border-color); 191 + border-radius: 0.375rem; 192 + font-size: 0.875rem; 193 + color: var(--text-color); 194 + background-color: var(--white); 195 + transition: border-color 0.2s; 196 + } 197 + 198 + .pds-verification input:focus { 199 + outline: none; 200 + border-color: var(--primary-color); 201 + } 202 + 203 + .pds-verification input.error { 204 + border-color: var(--error-color); 205 + } 206 + 207 + .error-message { 208 + color: var(--error-color); 209 + font-size: 0.875rem; 210 + margin: 0.5rem 0 0 0; 211 + } 212 + 213 + .warning-buttons button:disabled { 214 + opacity: 0.5; 215 + cursor: not-allowed; 216 + }
+22
src/css/footer.css
···
··· 1 + /* Footer styles */ 2 + .footer { 3 + text-align: center; 4 + padding: 1rem; 5 + color: var(--text-light); 6 + font-size: 0.875rem; 7 + margin: 1rem 0; 8 + } 9 + 10 + .footer-link { 11 + color: var(--primary-color); 12 + text-decoration: none; 13 + transition: color 0.2s; 14 + } 15 + 16 + .footer-link:hover { 17 + color: var(--primary-hover); 18 + } 19 + 20 + .footer-link strong { 21 + font-weight: 600; 22 + }
+94
src/css/forms.css
···
··· 1 + /* Form styles */ 2 + .form-input { 3 + width: 100%; 4 + padding: 0.75rem 1rem; 5 + border: 1px solid var(--border-color); 6 + border-radius: 0.375rem; 7 + font-size: 0.875rem; 8 + color: var(--text-color); 9 + background-color: var(--input-bg); 10 + box-sizing: border-box; 11 + } 12 + 13 + .form-input:focus { 14 + outline: none; 15 + border-color: var(--primary-color); 16 + box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1); 17 + } 18 + 19 + .form-input::placeholder { 20 + color: var(--text-light); 21 + } 22 + 23 + .form-input.error { 24 + border-color: var(--error-color); 25 + } 26 + 27 + .form-input.error:focus { 28 + box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.1); 29 + } 30 + 31 + .form-input:disabled { 32 + background-color: var(--bg-color); 33 + border-color: var(--border-color); 34 + color: var(--text-light); 35 + cursor: not-allowed; 36 + opacity: 0.8; 37 + } 38 + 39 + .error-message { 40 + color: var(--error-color); 41 + font-size: 0.875rem; 42 + margin-top: 0.25rem; 43 + } 44 + 45 + .success-message { 46 + color: #059669; 47 + font-size: 0.875rem; 48 + margin: 0.5rem 0; 49 + display: flex; 50 + align-items: center; 51 + gap: 0.5rem; 52 + } 53 + 54 + .form-section { 55 + background: var(--white); 56 + border-radius: 8px; 57 + padding: 2rem; 58 + margin-bottom: 2rem; 59 + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 60 + transition: all 0.3s ease; 61 + border: 1px solid var(--border-color); 62 + } 63 + 64 + .form-section.completed { 65 + opacity: 0.6; 66 + pointer-events: none; 67 + background: var(--bg-color); 68 + } 69 + 70 + .form-section h3 { 71 + margin-top: 0; 72 + margin-bottom: 1.5rem; 73 + color: var(--text-color); 74 + font-size: 1.25rem; 75 + } 76 + 77 + .info-message { 78 + color: var(--text-light); 79 + font-size: 0.875rem; 80 + margin-top: 0; 81 + margin-bottom: 1rem; 82 + padding: 1rem; 83 + background-color: rgba(76, 67, 249, 0.25); 84 + border-radius: 0.375rem; 85 + } 86 + 87 + .info-message h3 { 88 + margin-top: 0; 89 + margin-bottom: 0.5rem; 90 + } 91 + 92 + .info-message strong { 93 + word-break: break-all; 94 + }
+84
src/css/header.css
···
··· 1 + /* Header styles */ 2 + .app-header { 3 + display: flex; 4 + flex-wrap: wrap; 5 + align-items: center; 6 + justify-content: space-between; 7 + gap: 1rem; 8 + padding: 1rem; 9 + background-color: var(--white); 10 + border-radius: 0.5rem; 11 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 12 + margin: 1rem 0 0 0; 13 + position: relative; 14 + z-index: 10; 15 + } 16 + 17 + .app-title { 18 + font-size: 1.25rem; 19 + font-weight: 700; 20 + color: var(--text-color); 21 + margin: 0; 22 + white-space: nowrap; 23 + overflow: hidden; 24 + text-overflow: ellipsis; 25 + max-width: 200px; 26 + } 27 + 28 + .user-info { 29 + display: flex; 30 + align-items: center; 31 + gap: 0.75rem; 32 + flex-wrap: wrap; 33 + } 34 + 35 + .user-avatar { 36 + width: 2rem; 37 + height: 2rem; 38 + border-radius: 50%; 39 + object-fit: cover; 40 + flex-shrink: 0; 41 + } 42 + 43 + .user-handle { 44 + font-size: 0.875rem; 45 + color: var(--text-color); 46 + white-space: nowrap; 47 + overflow: hidden; 48 + text-overflow: ellipsis; 49 + max-width: 150px; 50 + } 51 + 52 + .logout-button { 53 + background-color: #f44336; 54 + color: white; 55 + border: none; 56 + padding: 8px 16px; 57 + border-radius: 4px; 58 + cursor: pointer; 59 + font-size: 14px; 60 + transition: background-color 0.2s; 61 + white-space: nowrap; 62 + flex-shrink: 0; 63 + } 64 + 65 + @media (max-width: 480px) { 66 + .app-header { 67 + padding: 0.75rem; 68 + gap: 0.5rem; 69 + } 70 + 71 + .app-title { 72 + font-size: 1.125rem; 73 + max-width: 150px; 74 + } 75 + 76 + .user-handle { 77 + max-width: 100px; 78 + } 79 + 80 + .logout-button { 81 + padding: 6px 12px; 82 + font-size: 13px; 83 + } 84 + }
+47
src/css/loading.css
···
··· 1 + /* Loading state styles */ 2 + .loading-container { 3 + flex: 1; 4 + display: flex; 5 + align-items: center; 6 + justify-content: center; 7 + min-height: 100vh; 8 + padding: 1rem; 9 + } 10 + 11 + .loading-text { 12 + font-size: 1.25rem; 13 + color: var(--text-color); 14 + background-color: var(--white); 15 + padding: 1.5rem 2rem; 16 + border-radius: 0.5rem; 17 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 18 + display: flex; 19 + align-items: center; 20 + gap: 0.75rem; 21 + } 22 + 23 + .loading-text::before { 24 + content: ""; 25 + width: 1.5rem; 26 + height: 1.5rem; 27 + border: 2px solid var(--primary-color); 28 + border-right-color: transparent; 29 + border-radius: 50%; 30 + animation: spin 1s linear infinite; 31 + } 32 + 33 + @keyframes spin { 34 + to { 35 + transform: rotate(360deg); 36 + } 37 + } 38 + 39 + .loading-message { 40 + margin: 10px 0; 41 + padding: 10px; 42 + background-color: var(--bg-color); 43 + border-radius: 4px; 44 + color: var(--text-color); 45 + font-size: 0.9em; 46 + text-align: center; 47 + }
+140
src/css/login.css
···
··· 1 + /* Login page styles */ 2 + .login-container { 3 + flex: 1; 4 + display: flex; 5 + align-items: center; 6 + justify-content: center; 7 + padding: 1rem; 8 + } 9 + 10 + .login-card { 11 + max-width: 28rem; 12 + width: 100%; 13 + padding: 2rem; 14 + padding-top: 0; 15 + background-color: var(--white); 16 + border-radius: 0.5rem; 17 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 18 + } 19 + 20 + .login-title { 21 + padding-top: 0; 22 + text-align: center; 23 + font-size: 1.875rem; 24 + font-weight: 800; 25 + color: var(--text-color); 26 + } 27 + 28 + .warning-message { 29 + margin: 1rem 0; 30 + padding: 0.75rem; 31 + background-color: #fef3c7; 32 + border: 1px solid #fbbf24; 33 + border-radius: 0.375rem; 34 + color: #92400e; 35 + font-size: 0.875rem; 36 + text-align: center; 37 + } 38 + 39 + .login-form { 40 + margin-top: 2rem; 41 + padding: 0 1rem; 42 + } 43 + 44 + .form-group { 45 + margin-bottom: 1rem; 46 + } 47 + 48 + .form-group label { 49 + display: block; 50 + margin-bottom: 0.5rem; 51 + color: var(--text-color); 52 + font-weight: 500; 53 + } 54 + 55 + .handle-input-container { 56 + display: flex; 57 + align-items: stretch; 58 + gap: 0; 59 + background-color: var(--input-bg); 60 + border: 1px solid var(--border-color); 61 + border-radius: 0.375rem; 62 + } 63 + 64 + .handle-input-container .form-input { 65 + border: none; 66 + padding-right: 0; 67 + flex: 1; 68 + border-radius: 0.375rem 0 0 0.375rem; 69 + } 70 + 71 + .handle-input-container .form-input:focus { 72 + box-shadow: none; 73 + } 74 + 75 + .handle-domain { 76 + color: var(--text-light); 77 + font-size: 0.875rem; 78 + white-space: nowrap; 79 + background-color: var(--bg-color); 80 + padding: 0 0.75rem; 81 + border-radius: 0 0.375rem 0.375rem 0; 82 + display: flex; 83 + align-items: center; 84 + } 85 + 86 + /* 2FA Modal styles */ 87 + .modal-overlay { 88 + position: fixed; 89 + top: 0; 90 + left: 0; 91 + right: 0; 92 + bottom: 0; 93 + background-color: rgba(0, 0, 0, 0.75); 94 + display: flex; 95 + justify-content: center; 96 + align-items: center; 97 + z-index: 1000; 98 + } 99 + 100 + .modal-content { 101 + background-color: var(--white); 102 + padding: 2rem; 103 + border-radius: 0.5rem; 104 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 105 + width: 90%; 106 + max-width: 28rem; 107 + } 108 + 109 + .modal-content h2 { 110 + margin-top: 0; 111 + margin-bottom: 1rem; 112 + color: var(--text-color); 113 + text-align: center; 114 + } 115 + 116 + .modal-content p { 117 + margin-bottom: 1.5rem; 118 + color: var(--text-light); 119 + text-align: center; 120 + } 121 + 122 + .two-factor-form { 123 + margin-top: 2rem; 124 + } 125 + 126 + .button-group { 127 + display: flex; 128 + gap: 1rem; 129 + margin-top: 1rem; 130 + } 131 + 132 + .button-group .submit-button { 133 + margin-top: 0; 134 + flex: 1; 135 + width: fit-content; 136 + } 137 + 138 + .button-group .back-button { 139 + flex: 1; 140 + }
+71
src/css/migration.css
···
··· 1 + /* Migration progress styles */ 2 + .migration-progress { 3 + margin-top: 2rem; 4 + padding: 1.5rem; 5 + background-color: var(--white); 6 + border-radius: 0.5rem; 7 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 8 + } 9 + 10 + .migration-progress h3 { 11 + margin: 0 0 1rem 0; 12 + color: var(--text-color); 13 + } 14 + 15 + .progress-steps { 16 + display: flex; 17 + flex-direction: column; 18 + gap: 1rem; 19 + } 20 + 21 + .progress-step { 22 + display: flex; 23 + align-items: center; 24 + gap: 1rem; 25 + padding: 0.75rem; 26 + border-radius: 0.5rem; 27 + background-color: var(--bg-color); 28 + transition: background-color 0.2s; 29 + } 30 + 31 + .progress-step.active { 32 + background-color: var(--primary-color); 33 + color: white; 34 + } 35 + 36 + .step-number { 37 + width: 2rem; 38 + height: 2rem; 39 + display: flex; 40 + align-items: center; 41 + justify-content: center; 42 + background-color: var(--white); 43 + border-radius: 50%; 44 + font-weight: bold; 45 + color: var(--text-color); 46 + } 47 + 48 + .progress-step.active .step-number { 49 + background-color: var(--white); 50 + color: var(--primary-color); 51 + } 52 + 53 + .step-text { 54 + font-size: 0.875rem; 55 + } 56 + 57 + @media (max-width: 480px) { 58 + .migration-progress { 59 + padding: 1rem; 60 + } 61 + 62 + .progress-step { 63 + padding: 0.5rem; 64 + } 65 + 66 + .step-number { 67 + width: 1.75rem; 68 + height: 1.75rem; 69 + font-size: 0.875rem; 70 + } 71 + }
+91
src/css/plcToken.css
···
··· 1 + /* PLC token section styles */ 2 + .plc-token-section { 3 + background: #f8f9fa; 4 + border-radius: 8px; 5 + padding: 20px; 6 + margin: 15px 0; 7 + } 8 + 9 + .token-request-container { 10 + text-align: center; 11 + } 12 + 13 + .token-request-container p { 14 + margin-bottom: 15px; 15 + color: #666; 16 + } 17 + 18 + .token-input-container { 19 + max-width: 500px; 20 + margin: 0 auto; 21 + } 22 + 23 + .token-input-container p { 24 + margin-bottom: 10px; 25 + color: #666; 26 + } 27 + 28 + .token-input-group { 29 + display: flex; 30 + gap: 10px; 31 + margin-bottom: 15px; 32 + } 33 + 34 + .token-input { 35 + flex: 1; 36 + padding: 10px; 37 + border: 1px solid #ddd; 38 + border-radius: 4px; 39 + font-size: 14px; 40 + } 41 + 42 + .request-token-button, 43 + .submit-token-button, 44 + .request-new-token-button { 45 + padding: 10px 20px; 46 + border: none; 47 + border-radius: 4px; 48 + font-size: 14px; 49 + cursor: pointer; 50 + transition: background-color 0.2s; 51 + } 52 + 53 + .request-token-button { 54 + background-color: #007bff; 55 + color: white; 56 + } 57 + 58 + .request-token-button:hover { 59 + background-color: #0056b3; 60 + } 61 + 62 + .submit-token-button { 63 + background-color: #28a745; 64 + color: white; 65 + } 66 + 67 + .submit-token-button:hover { 68 + background-color: #218838; 69 + } 70 + 71 + .submit-token-button:disabled { 72 + background-color: #6c757d; 73 + cursor: not-allowed; 74 + } 75 + 76 + .request-new-token-button { 77 + background-color: transparent; 78 + color: #007bff; 79 + border: 1px solid #007bff; 80 + width: 100%; 81 + } 82 + 83 + .request-new-token-button:hover:not(:disabled) { 84 + background-color: #f0f7ff; 85 + } 86 + 87 + .request-new-token-button:disabled { 88 + color: #6c757d; 89 + border-color: #6c757d; 90 + cursor: not-allowed; 91 + }
+21
src/hooks/useDebounce.ts
···
··· 1 + import { useCallback, useRef } from 'react'; 2 + 3 + export function useDebounce<T extends (...args: any[]) => void>( 4 + callback: T, 5 + delay: number 6 + ): T { 7 + const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined); 8 + 9 + return useCallback( 10 + (...args: Parameters<T>) => { 11 + if (timeoutRef.current) { 12 + clearTimeout(timeoutRef.current); 13 + } 14 + 15 + timeoutRef.current = setTimeout(() => { 16 + callback(...args); 17 + }, delay); 18 + }, 19 + [callback, delay] 20 + ) as T; 21 + }
+24
src/lib/migration/accountDetailsValidation.ts
···
··· 1 + export const validateHandle = (handle: string, availableUserDomains: string[]): { 2 + isUsingDefaultDomain: boolean; 3 + customHandle: string; 4 + } => { 5 + // Check if handle ends with any of the available user domains 6 + const isUsingDefaultDomain = availableUserDomains.some(domain => 7 + handle.endsWith(domain) 8 + ); 9 + 10 + if (!isUsingDefaultDomain) { 11 + return { 12 + isUsingDefaultDomain: false, 13 + customHandle: handle 14 + }; 15 + } 16 + 17 + // Extract the custom part of the handle (everything before the domain) 18 + const customHandle = handle.split('.')[0]; 19 + 20 + return { 21 + isUsingDefaultDomain: true, 22 + customHandle: customHandle 23 + }; 24 + };
+43
src/lib/migration/migrationData.ts
···
··· 1 + import { AtpAgent } from "@atproto/api"; 2 + 3 + export class MigrationData { 4 + private readonly oldPds: String; 5 + private readonly newPds: String; 6 + private readonly inviteCode: String | null; 7 + private readonly handle: String; 8 + private readonly email: String; 9 + private readonly password: String; 10 + 11 + constructor(oldPds: String, newPds: String, inviteCode: String | null, handle: String, email: String, password: String) { 12 + this.oldPds = oldPds; 13 + this.newPds = newPds; 14 + this.inviteCode = inviteCode; 15 + this.handle = handle; 16 + this.email = email; 17 + this.password = password; 18 + } 19 + 20 + public getOldPds(): String { 21 + return this.oldPds; 22 + } 23 + 24 + public getNewPds(): String { 25 + return this.newPds; 26 + } 27 + 28 + public getInviteCode(): String | null { 29 + return this.inviteCode; 30 + } 31 + 32 + public getHandle(): String { 33 + return this.handle; 34 + } 35 + 36 + public getEmail(): String { 37 + return this.email; 38 + } 39 + 40 + public getPassword(): String { 41 + return this.password; 42 + } 43 + }
+236
src/lib/migration/pdsValidation.ts
···
··· 1 + import { AtpAgent } from '@atproto/api'; 2 + import { ServerDescription } from './serverDescription'; 3 + 4 + export interface ValidationResult { 5 + isValid: boolean; 6 + error: string | null; 7 + } 8 + 9 + const ensureProtocol = (url: string): string => { 10 + if (url.startsWith('http://') || url.startsWith('https://')) { 11 + return url; 12 + } 13 + return `https://${url}`; 14 + }; 15 + 16 + const isValidUrl = (url: string): boolean => { 17 + try { 18 + new URL(url); 19 + return true; 20 + } catch { 21 + return false; 22 + } 23 + }; 24 + 25 + const checkPDSHealth = async (pdsUrl: string): Promise<boolean> => { 26 + const controller = new AbortController(); 27 + const timeoutId = setTimeout(() => controller.abort(), 15000); 28 + 29 + try { 30 + const response = await fetch(`${pdsUrl}/xrpc/_health`, { 31 + signal: controller.signal 32 + }); 33 + if (!response.ok) return false; 34 + 35 + const data = await response.json(); 36 + // Check if response is just a version number 37 + return typeof data === 'string' || (typeof data === 'object' && Object.keys(data).length === 1 && typeof data.version === 'string'); 38 + } catch (e) { 39 + if (e instanceof Error && e.name === 'AbortError') { 40 + throw new Error('Request timed out'); 41 + } 42 + return false; 43 + } finally { 44 + clearTimeout(timeoutId); 45 + } 46 + }; 47 + 48 + const getServerDescription = async (pdsUrl: string): Promise<ServerDescription> => { 49 + const controller = new AbortController(); 50 + const timeoutId = setTimeout(() => controller.abort(), 15000); 51 + 52 + try { 53 + const response = await fetch(`https://${pdsUrl}/xrpc/com.atproto.server.describeServer`, { 54 + signal: controller.signal 55 + }); 56 + if (!response.ok) { 57 + const errorData = await response.text(); 58 + console.error('Server description request failed:', { 59 + status: response.status, 60 + statusText: response.statusText, 61 + url: pdsUrl, 62 + response: errorData 63 + }); 64 + throw new Error('Failed to get server description'); 65 + } 66 + 67 + const data = await response.json(); 68 + console.log('Server description response:', { 69 + url: pdsUrl, 70 + data 71 + }); 72 + 73 + return new ServerDescription({ 74 + did: data.did, 75 + availableUserDomains: data.availableUserDomains ?? {}, 76 + inviteCodeRequired: data.inviteCodeRequired ?? false, 77 + links: data.links ?? {}, 78 + contact: data.contact ?? {} 79 + }); 80 + } catch (e) { 81 + console.error('Error getting server description:', { 82 + url: pdsUrl, 83 + error: e instanceof Error ? { 84 + name: e.name, 85 + message: e.message, 86 + stack: e.stack 87 + } : e 88 + }); 89 + if (e instanceof Error && e.name === 'AbortError') { 90 + throw new Error('Request timed out'); 91 + } 92 + throw e; 93 + } finally { 94 + clearTimeout(timeoutId); 95 + } 96 + }; 97 + 98 + export const validateInviteCode = async (pdsUrl: string, inviteCode: string): Promise<ValidationResult> => { 99 + inviteCode = inviteCode.trim(); 100 + 101 + try { 102 + if (!inviteCode) { 103 + return { 104 + isValid: false, 105 + error: 'This server requires an invite code' 106 + }; 107 + } 108 + 109 + const pdsPart = pdsUrl.replace(/\./g, '-'); 110 + const inviteCodeRegex = new RegExp(`^${pdsPart}-[a-zA-Z0-9]{5}-[a-zA-Z0-9]{5}$`); 111 + const isValid = inviteCodeRegex.test(inviteCode); 112 + 113 + if (!isValid) { 114 + console.error('Invalid invite code format:', { 115 + pds: pdsUrl, 116 + inviteCode, 117 + expectedFormat: `${pdsUrl.replace(/\./g, '-')}-XXXXX-XXXXX` 118 + }); 119 + return { 120 + isValid: false, 121 + error: 'Incorrect invite code format' 122 + }; 123 + } 124 + 125 + return { 126 + isValid: true, 127 + error: null 128 + }; 129 + } catch (e) { 130 + console.error('Error validating invite code:', { 131 + pds: pdsUrl, 132 + inviteCode, 133 + error: e instanceof Error ? { 134 + name: e.name, 135 + message: e.message, 136 + stack: e.stack 137 + } : e 138 + }); 139 + return { 140 + isValid: false, 141 + error: 'Failed to validate invite code' 142 + }; 143 + } 144 + }; 145 + 146 + export const validatePDS = async (pdsUrl: string, agent: AtpAgent): Promise<ValidationResult> => { 147 + if (!pdsUrl.trim()) { 148 + return { 149 + isValid: false, 150 + error: null 151 + }; 152 + } 153 + 154 + const urlWithProtocol = ensureProtocol(pdsUrl); 155 + 156 + if (!isValidUrl(urlWithProtocol)) { 157 + return { 158 + isValid: false, 159 + error: 'Please enter a valid URL' 160 + }; 161 + } 162 + 163 + try { 164 + const url = new URL(urlWithProtocol); 165 + const hostname = url.hostname; 166 + 167 + // Check for Bluesky domains 168 + if (hostname.endsWith('bsky.network') || 169 + hostname.endsWith('bsky.social') || 170 + hostname.endsWith('bsky.app')) { 171 + return { 172 + isValid: false, 173 + error: 'You cannot migrate to Bluesky data servers at this time.' 174 + }; 175 + } 176 + 177 + // Check if trying to migrate to current PDS 178 + const currentPDS = agent.serviceUrl; 179 + if (hostname === new URL(currentPDS).hostname) { 180 + return { 181 + isValid: false, 182 + error: 'You cannot migrate to the same PDS that you are currently using.' 183 + }; 184 + } 185 + 186 + // Check if PDS is alive and valid 187 + try { 188 + const isHealthy = await checkPDSHealth(urlWithProtocol); 189 + if (!isHealthy) { 190 + return { 191 + isValid: false, 192 + error: 'This PDS is either offline or not a valid server.' 193 + }; 194 + } 195 + } catch (e) { 196 + console.error('Error checking PDS health:', { 197 + url: urlWithProtocol, 198 + error: e instanceof Error ? { 199 + name: e.name, 200 + message: e.message, 201 + stack: e.stack 202 + } : e 203 + }); 204 + if (e instanceof Error && e.message === 'Request timed out') { 205 + return { 206 + isValid: false, 207 + error: 'The server took too long to respond. Please try again later.' 208 + }; 209 + } 210 + return { 211 + isValid: false, 212 + error: 'This PDS is either offline or not a valid server.' 213 + }; 214 + } 215 + 216 + return { 217 + isValid: true, 218 + error: null 219 + }; 220 + } catch (e) { 221 + console.error('Error validating PDS:', { 222 + url: pdsUrl, 223 + error: e instanceof Error ? { 224 + name: e.name, 225 + message: e.message, 226 + stack: e.stack 227 + } : e 228 + }); 229 + return { 230 + isValid: false, 231 + error: 'Please enter a valid URL' 232 + }; 233 + } 234 + }; 235 + 236 + export { getServerDescription };
+48
src/lib/migration/serverDescription.ts
···
··· 1 + export class ServerDescription { 2 + private readonly did: string; 3 + private readonly availableUserDomains: Record<number, string>; 4 + private readonly inviteCodeRequired: boolean; 5 + private readonly phoneVerificationRequired: boolean; 6 + private readonly links: Record<string, string>; 7 + private readonly contact: Record<string, string>; 8 + 9 + constructor(data: { 10 + did: string; 11 + availableUserDomains: Record<number, string>; 12 + inviteCodeRequired: boolean; 13 + phoneVerificationRequired?: boolean; 14 + links: Record<string, string>; 15 + contact: Record<string, string>; 16 + }) { 17 + this.did = data.did; 18 + this.availableUserDomains = data.availableUserDomains; 19 + this.inviteCodeRequired = data.inviteCodeRequired; 20 + this.phoneVerificationRequired = data.phoneVerificationRequired ?? false; 21 + this.links = data.links; 22 + this.contact = data.contact; 23 + } 24 + 25 + getDid(): string { 26 + return this.did; 27 + } 28 + 29 + getAvailableUserDomains(): Record<number, string> { 30 + return this.availableUserDomains; 31 + } 32 + 33 + isInviteCodeRequired(): boolean { 34 + return this.inviteCodeRequired; 35 + } 36 + 37 + isPhoneVerificationRequired(): boolean { 38 + return this.phoneVerificationRequired; 39 + } 40 + 41 + getLinks(): Record<string, string> { 42 + return this.links; 43 + } 44 + 45 + getContact(): Record<string, string> { 46 + return this.contact; 47 + } 48 + }
+11
src/main.css
···
··· 1 + @import 'css/base.css'; 2 + @import 'css/header.css'; 3 + @import 'css/footer.css'; 4 + @import 'css/login.css'; 5 + @import 'css/forms.css'; 6 + @import 'css/buttons.css'; 7 + @import 'css/loading.css'; 8 + @import 'css/migration.css'; 9 + @import 'css/plcToken.css'; 10 + @import 'css/actions.css'; 11 + @import 'css/confirmation.css';
+72 -3
src/main.tsx
··· 1 - import { StrictMode } from 'react' 2 import { createRoot } from 'react-dom/client' 3 - import './styles/App.css' 4 - import App from './App.tsx' 5 6 createRoot(document.getElementById('root')!).render( 7 <StrictMode> 8 <App />
··· 1 + import { StrictMode, useState, useEffect } from 'react' 2 import { createRoot } from 'react-dom/client' 3 + import { BrowserRouter as Router } from 'react-router-dom' 4 + import { AtpAgent } from '@atproto/api' 5 + import { AvatarProvider } from './contexts/AvatarContext' 6 + import { NetworkProvider } from './contexts/NetworkContext' 7 + import { AppRoutes } from './Routes' 8 + import './main.css' 9 + 10 + const SESSION_KEY = 'atproto_session'; 11 + const SESSION_EXPIRY = 60 * 60 * 1000; // 1 hour in milliseconds 12 + 13 + function App() { 14 + const [agent, setAgent] = useState<AtpAgent | null>(null) 15 + 16 + useEffect(() => { 17 + // Load session from localStorage on initial load 18 + const loadSession = async () => { 19 + const savedSession = localStorage.getItem(SESSION_KEY); 20 + if (savedSession) { 21 + const { session, service, timestamp } = JSON.parse(savedSession); 22 + 23 + // Check if session is expired 24 + if (Date.now() - timestamp > SESSION_EXPIRY) { 25 + localStorage.removeItem(SESSION_KEY); 26 + return; 27 + } 28 + 29 + const newAgent = new AtpAgent({ service }); 30 + await newAgent.resumeSession(session); 31 + setAgent(newAgent); 32 + } 33 + }; 34 + 35 + loadSession(); 36 + }, []); 37 + 38 + const handleLogin = (newAgent: AtpAgent) => { 39 + setAgent(newAgent); 40 + // Save session to localStorage 41 + localStorage.setItem(SESSION_KEY, JSON.stringify({ 42 + session: newAgent.session, 43 + service: newAgent.serviceUrl, 44 + timestamp: Date.now() 45 + })); 46 + }; 47 + 48 + const handleLogout = () => { 49 + setAgent(null); 50 + localStorage.removeItem(SESSION_KEY); 51 + // Clear avatar URL from context 52 + const avatarContext = document.querySelector('[data-avatar-context]'); 53 + if (avatarContext) { 54 + const event = new CustomEvent('clearAvatar'); 55 + avatarContext.dispatchEvent(event); 56 + } 57 + }; 58 59 + return ( 60 + <NetworkProvider> 61 + <AvatarProvider> 62 + <Router> 63 + <AppRoutes 64 + agent={agent} 65 + onLogout={handleLogout} 66 + handleLogin={handleLogin} 67 + /> 68 + </Router> 69 + </AvatarProvider> 70 + </NetworkProvider> 71 + ) 72 + } 73 + 74 + // Initialize the app 75 createRoot(document.getElementById('root')!).render( 76 <StrictMode> 77 <App />
+130
src/pages/Actions.tsx
···
··· 1 + import { useEffect, useState } from 'react'; 2 + import { AtpAgent } from '@atproto/api'; 3 + import { useNavigate } from 'react-router-dom'; 4 + import Footer from '../components/common/Footer'; 5 + import Header from '../components/common/Header'; 6 + 7 + interface ActionsProps { 8 + agent: AtpAgent; 9 + onLogout: () => void; 10 + } 11 + 12 + export default function Actions({ agent, onLogout }: ActionsProps) { 13 + const [didDoc, setDidDoc] = useState<string>(''); 14 + const [loading, setLoading] = useState(true); 15 + const navigate = useNavigate(); 16 + 17 + const handleLogout = () => { 18 + onLogout(); 19 + navigate('/'); 20 + }; 21 + 22 + useEffect(() => { 23 + const fetchProfile = async () => { 24 + try { 25 + const did = agent.session?.did; 26 + if (!did) { 27 + throw new Error('No DID found in session'); 28 + } 29 + 30 + let didDocResponse; 31 + 32 + if (did.startsWith('did:plc:')) { 33 + // For PLC DIDs, resolve from plc.directory 34 + const response = await fetch(`https://plc.directory/${did}`); 35 + didDocResponse = await response.json(); 36 + } else if (did.startsWith('did:web:')) { 37 + // For Web DIDs, get from .well-known/did.json 38 + const domain = did.split(':')[2]; 39 + const response = await fetch(`https://${domain}/.well-known/did.json`); 40 + didDocResponse = await response.json(); 41 + } else { 42 + throw new Error(`Unsupported DID type: ${did}`); 43 + } 44 + 45 + setDidDoc(JSON.stringify(didDocResponse, null, 2)); 46 + } catch (err) { 47 + console.error('Error fetching DID document:', err); 48 + setDidDoc(`Error fetching DID document: ${err instanceof Error ? err.message : 'Unknown error'}`); 49 + } finally { 50 + setLoading(false); 51 + } 52 + }; 53 + 54 + fetchProfile(); 55 + }, [agent]); 56 + 57 + if (loading) { 58 + return ( 59 + <div className="loading-container"> 60 + <div className="loading-text">Loading...</div> 61 + </div> 62 + ); 63 + } 64 + 65 + return ( 66 + <div className="actions-page"> 67 + <Header agent={agent} onLogout={handleLogout} /> 68 + 69 + <div className="actions-container"> 70 + <div className="actions-list"> 71 + <button 72 + className="action-item" 73 + onClick={() => navigate('/migration')} 74 + > 75 + <div className="action-icon"> 76 + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> 77 + <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" /> 78 + </svg> 79 + </div> 80 + <div className="action-content"> 81 + <div className="action-title">Migrate account</div> 82 + <div className="action-subtitle">Move your account to a new data server</div> 83 + </div> 84 + </button> 85 + 86 + <button 87 + className="action-item" 88 + onClick={() => navigate('/recovery-key')} 89 + > 90 + <div className="action-icon"> 91 + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> 92 + <rect x="3" y="11" width="18" height="11" rx="2" ry="2" /> 93 + <path d="M7 11V7a5 5 0 0 1 10 0v4" /> 94 + </svg> 95 + </div> 96 + <div className="action-content"> 97 + <div className="action-title">Add recovery key</div> 98 + <div className="action-subtitle">Create a new recovery key for your account</div> 99 + </div> 100 + </button> 101 + </div> 102 + 103 + <details className="user-info-section"> 104 + <summary className="user-info-summary">User Information</summary> 105 + <div className="user-info-content"> 106 + <section> 107 + <h2>Account Details</h2> 108 + <dl> 109 + <dt>DID</dt> 110 + <dd>{agent.session?.did || 'N/A'}</dd> 111 + <dt>Handle</dt> 112 + <dd>{agent.session?.handle || 'N/A'}</dd> 113 + <dt>PDS</dt> 114 + <dd>{agent.serviceUrl.toString() || 'N/A'}</dd> 115 + </dl> 116 + </section> 117 + 118 + <section> 119 + <h2>DID Document</h2> 120 + <pre className="did-document"> 121 + <code>{didDoc}</code> 122 + </pre> 123 + </section> 124 + </div> 125 + </details> 126 + </div> 127 + <Footer /> 128 + </div> 129 + ); 130 + }
+266
src/pages/Login.tsx
···
··· 1 + import { useState } from 'react'; 2 + import { useNavigate } from 'react-router-dom'; 3 + import { AtpAgent } from '@atproto/api'; 4 + import Footer from '../components/common/Footer'; 5 + 6 + interface LoginProps { 7 + onLogin: (agent: AtpAgent) => void; 8 + } 9 + 10 + interface DidDocument { 11 + service: Array<{ 12 + id: string; 13 + type: string; 14 + serviceEndpoint: string; 15 + }>; 16 + } 17 + 18 + type LoginStep = 'idle' | 'resolving-handle' | 'resolving-did' | 'connecting-pds' | 'authenticating' | '2fa-required' | 'success'; 19 + 20 + export default function Login({ onLogin }: LoginProps) { 21 + const [handle, setHandle] = useState(''); 22 + const [password, setPassword] = useState(''); 23 + const [twoFactorCode, setTwoFactorCode] = useState(''); 24 + const [error, setError] = useState(''); 25 + const [appPasswordAttempts, setAppPasswordAttempts] = useState(0); 26 + const [loginStep, setLoginStep] = useState<LoginStep>('idle'); 27 + const [agent, setAgent] = useState<AtpAgent | null>(null); 28 + const navigate = useNavigate(); 29 + 30 + const isAppPassword = (password: string) => { 31 + // App passwords are typically in the format xxxx-xxxx-xxxx-xxxx 32 + return /^[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$/.test(password); 33 + }; 34 + 35 + const getStepMessage = (step: LoginStep) => { 36 + switch (step) { 37 + case 'resolving-handle': 38 + return 'Resolving your handle...'; 39 + case 'resolving-did': 40 + return 'Resolving your DID...'; 41 + case 'connecting-pds': 42 + return 'Connecting to your Personal Data Server...'; 43 + case 'authenticating': 44 + return 'Authenticating your credentials...'; 45 + case '2fa-required': 46 + return 'Please enter your 2FA code'; 47 + case 'success': 48 + return 'Login successful! Redirecting...'; 49 + default: 50 + return ''; 51 + } 52 + }; 53 + 54 + const handleSubmit = async (e: React.FormEvent) => { 55 + e.preventDefault(); 56 + setError(''); 57 + 58 + // If we're in 2FA step, handle that separately 59 + if (loginStep === '2fa-required') { 60 + if (!agent) { 61 + setError('Session expired. Please try logging in again.'); 62 + setLoginStep('idle'); 63 + return; 64 + } 65 + 66 + try { 67 + setLoginStep('authenticating'); 68 + await agent.login({ identifier: handle, password, authFactorToken: twoFactorCode }); 69 + setLoginStep('success'); 70 + onLogin(agent); 71 + navigate('/actions'); 72 + } catch (err) { 73 + setError(err instanceof Error ? err.message : '2FA verification failed'); 74 + setLoginStep('2fa-required'); 75 + } 76 + return; 77 + } 78 + 79 + setLoginStep('resolving-handle'); 80 + 81 + // app password check and debug method 82 + if (isAppPassword(password)) { 83 + if (appPasswordAttempts < 3) { 84 + setAppPasswordAttempts(appPasswordAttempts + 1); 85 + setError(`You have entered an app password, which does not allow for you to migrate your account. Please enter your main account password instead.`); 86 + setLoginStep('idle'); 87 + return; 88 + } 89 + } 90 + 91 + setHandle(handle.trim()); 92 + 93 + try { 94 + // Create temporary agent to resolve DID 95 + const tempAgent = new AtpAgent({ service: 'https://public.api.bsky.app' }); 96 + 97 + // Get DID document from handle 98 + setLoginStep('resolving-handle'); 99 + const didResponse = await tempAgent.com.atproto.identity.resolveHandle({ 100 + handle: handle 101 + }); 102 + 103 + if (!didResponse.success) { 104 + // Try did:web resolution first 105 + const domain = handle.split('.').join(':'); 106 + const webDid = `did:web:${domain}`; 107 + try { 108 + const webResponse = await fetch(`https://${handle}/.well-known/did.json`); 109 + if (webResponse.ok) { 110 + // If successful, continue with the did:web 111 + didResponse.data.did = webDid; 112 + } else { 113 + throw new Error('Invalid handle'); 114 + } 115 + } catch { 116 + throw new Error('Invalid handle'); 117 + } 118 + } 119 + 120 + // Get PDS endpoint from DID document 121 + setLoginStep('resolving-did'); 122 + let didDocResponse; 123 + const did = didResponse.data.did; 124 + 125 + if (did.startsWith('did:plc:')) { 126 + // For PLC DIDs, resolve from plc.directory 127 + const plcResponse = await fetch(`https://plc.directory/${did}`); 128 + didDocResponse = { data: await plcResponse.json() }; 129 + } else if (did.startsWith('did:web:')) { 130 + // For Web DIDs, get from .well-known/did.json 131 + const domain = did.split(':')[2]; 132 + const webResponse = await fetch(`https://${domain}/.well-known/did.json`); 133 + didDocResponse = { data: await webResponse.json() }; 134 + } else { 135 + // Fallback to ATP resolver for other DID types 136 + didDocResponse = await tempAgent.com.atproto.identity.resolveDid({ 137 + did: did 138 + }); 139 + } 140 + 141 + setLoginStep('connecting-pds'); 142 + const pds = ((didDocResponse.data as unknown) as DidDocument).service.find((s) => s.id === '#atproto_pds')?.serviceEndpoint || 'https://bsky.social'; 143 + 144 + const newAgent = new AtpAgent({ service: pds }); 145 + setAgent(newAgent); 146 + 147 + setLoginStep('authenticating'); 148 + try { 149 + await newAgent.login({ identifier: handle, password }); 150 + setLoginStep('success'); 151 + onLogin(newAgent); 152 + navigate('/actions'); 153 + } catch (err) { 154 + if (err instanceof Error && ( 155 + err.message.includes('AuthFactorTokenRequired') || 156 + err.message.includes('A sign in code has been sent to your email address') 157 + )) { 158 + setLoginStep('2fa-required'); 159 + return; 160 + } 161 + throw err; 162 + } 163 + } catch (err) { 164 + setError(err instanceof Error ? err.message : 'Login failed'); 165 + setLoginStep('idle'); 166 + } 167 + }; 168 + 169 + return ( 170 + <div> 171 + <h1 className="login-title">ATproto Migrator</h1> 172 + <div className="login-container"> 173 + <div className="login-card"> 174 + <h2 className="login-title">Sign in to your account</h2> 175 + <div className="warning-message"> 176 + โš ๏ธ Please use your main account password, not an app password. All operations are performed locally in your browser. 177 + </div> 178 + <form className="login-form" onSubmit={handleSubmit}> 179 + <div className="form-group"> 180 + <input 181 + type="text" 182 + required 183 + className="form-input" 184 + placeholder="Handle (e.g., example.bsky.social)" 185 + value={handle} 186 + onChange={(e) => setHandle(e.target.value)} 187 + disabled={loginStep !== 'idle'} 188 + /> 189 + </div> 190 + <div className="form-group"> 191 + <input 192 + type="password" 193 + required 194 + className="form-input" 195 + placeholder="Password" 196 + value={password} 197 + onChange={(e) => setPassword(e.target.value)} 198 + disabled={loginStep !== 'idle'} 199 + /> 200 + </div> 201 + 202 + {error && <div className="error-message">{error}</div>} 203 + {loginStep !== 'idle' && loginStep !== '2fa-required' && ( 204 + <div className="loading-message"> 205 + {getStepMessage(loginStep)} 206 + </div> 207 + )} 208 + 209 + <button 210 + type="submit" 211 + className="submit-button" 212 + disabled={loginStep !== 'idle'} 213 + > 214 + {loginStep === 'idle' ? 'Sign in' : 'Signing in...'} 215 + </button> 216 + </form> 217 + </div> 218 + </div> 219 + 220 + {/* 2FA Modal */} 221 + {loginStep === '2fa-required' && ( 222 + <div className="modal-overlay"> 223 + <div className="modal-content"> 224 + <h2>Two-Factor Authentication Required</h2> 225 + <p>A sign in code has been sent to your email address.</p> 226 + <form onSubmit={handleSubmit} className="two-factor-form"> 227 + <div className="form-group"> 228 + <input 229 + type="text" 230 + required 231 + className="form-input" 232 + placeholder="Enter 2FA code" 233 + value={twoFactorCode} 234 + onChange={(e) => setTwoFactorCode(e.target.value)} 235 + autoFocus 236 + /> 237 + </div> 238 + {error && <div className="error-message">{error}</div>} 239 + <div className="button-group"> 240 + <button 241 + type="button" 242 + className="back-button" 243 + onClick={() => { 244 + setLoginStep('idle'); 245 + setError(''); 246 + setTwoFactorCode(''); 247 + }} 248 + > 249 + Cancel 250 + </button> 251 + <button 252 + type="submit" 253 + className="submit-button" 254 + > 255 + Verify 256 + </button> 257 + </div> 258 + </form> 259 + </div> 260 + </div> 261 + )} 262 + 263 + <Footer /> 264 + </div> 265 + ); 266 + }
+35
src/pages/error.tsx
···
··· 1 + import React from 'react'; 2 + import { useNavigate } from 'react-router-dom'; 3 + 4 + interface ErrorPageProps { 5 + statusCode?: number; 6 + message?: string; 7 + } 8 + 9 + const ErrorPage: React.FC<ErrorPageProps> = ({ 10 + statusCode = 404, 11 + message = "The page you're looking for doesn't exist." 12 + }) => { 13 + const navigate = useNavigate(); 14 + 15 + return ( 16 + <div className="page-content" style={{ textAlign: 'center', maxWidth: '600px', margin: '4rem auto' }}> 17 + <h2 style={{ fontSize: '3rem', marginBottom: '1rem', color: 'var(--error-color)' }}> 18 + {statusCode} 19 + </h2> 20 + <p style={{ fontSize: '1.25rem', marginBottom: '2rem' }}> 21 + {message} 22 + </p> 23 + <div className="button-container" style={{ display: 'inline' }}> 24 + <button 25 + className="continue-button" 26 + onClick={() => navigate('/')} 27 + > 28 + Come back home 29 + </button> 30 + </div> 31 + </div> 32 + ); 33 + }; 34 + 35 + export default ErrorPage;
+72
src/pages/migration/Migration.tsx
···
··· 1 + import { useNavigate } from 'react-router-dom'; 2 + import { AtpAgent } from '@atproto/api'; 3 + import Header from '../../components/common/Header'; 4 + import Footer from '../../components/common/Footer'; 5 + 6 + interface MigrationProps { 7 + agent: AtpAgent; 8 + onLogout: () => void; 9 + } 10 + 11 + export default function Migration({ agent, onLogout }: MigrationProps) { 12 + const navigate = useNavigate(); 13 + 14 + return ( 15 + <div className="actions-page"> 16 + <Header agent={agent} onLogout={onLogout} /> 17 + 18 + <div className="actions-container"> 19 + <div className="page-content"> 20 + <h2>Migrate your account</h2> 21 + <p>This tool allows you to migrate your account to a new Personal Data Server, a data server that hosts your account and all of its data.</p> 22 + <h3>What to expect</h3> 23 + <p>The migration process is <i>possible</i>, however it is not recommended if you are unsure about what you are doing. We recommend that you migrate a secondary account to your new PDS to make sure that it is able to migrate successfully <i>before</i> migrating your primary account.</p> 24 + <p>You will need the following items to begin the migration process:</p> 25 + <ul> 26 + <li>A new PDS to migrate to.</li> 27 + <li>An invite code from the new PDS (if required).</li> 28 + <li>A way to confirm the migration, which is either a code sent to your email or your private rotation key.</li> 29 + <li>A new password for your account <b>(which will not be stored by this tool)</b>.</li> 30 + </ul> 31 + 32 + <div className="warning-section"> 33 + <h3>โš ๏ธ Read Before You Continue โš ๏ธ</h3> 34 + <ul> 35 + <li>If you are already on a third-party PDS, it must be able to send emails or you will be unable to get a confirmation code without direct server access. We are currently unable to check this for you, however if you can verify your email address the server supports it.</li> 36 + <li>If you are not using a custom domain, you will need a new handle as the default domain (such as alice.bsky.social or bob.pds.example.com) is non-transferable.</li> 37 + <li>If your account is using the did:web method, you will need to modify your DID document manually. If you don't know what this means, you don't need to worry about it.</li> 38 + <li>Due to performance issues, the main Bluesky data servers have temporarily disabled the ability to import account data. <b>As a result, you cannot migrate back to Bluesky servers for the foreseeable future.</b></li> 39 + <li>If your PDS goes down and you do not have access to a recovery key, you will be locked out of your account. <b>Bluesky developers will not be able to help you.</b></li> 40 + </ul> 41 + </div> 42 + 43 + <div className="docs-section"> 44 + <h3>Additional Resources</h3> 45 + <p>For the technically inclined, here are some additional resources for how the migration process works:</p> 46 + <ul> 47 + <li><a href="https://github.com/bluesky-social/pds/blob/main/ACCOUNT_MIGRATION.md" target="_blank" rel="noopener noreferrer">Detailed document on migration for PDS hosters</a></li> 48 + <li><a href="https://atproto.com/guides/account-migration" target="_blank" rel="noopener noreferrer">AT Protocol's developer documentation on account migration</a></li> 49 + <li><a href="https://whtwnd.com/bnewbold.net/3l5ii332pf32u">Guide to migrating an account using the command line</a></li> 50 + </ul> 51 + </div> 52 + 53 + <div className="button-container"> 54 + <button 55 + className="back-button" 56 + onClick={() => navigate('/actions')} 57 + > 58 + โ† Go back 59 + </button> 60 + <button 61 + className="continue-button" 62 + onClick={() => navigate('/migration/registration')} 63 + > 64 + Continue โ†’ 65 + </button> 66 + </div> 67 + </div> 68 + </div> 69 + <Footer /> 70 + </div> 71 + ); 72 + }
+82
src/pages/migration/MigrationProcess.tsx
···
··· 1 + import { useEffect, useState } from 'react'; 2 + import { useNavigate } from 'react-router-dom'; 3 + import { AtpAgent } from '@atproto/api'; 4 + import Header from '../../components/common/Header'; 5 + import Footer from '../../components/common/Footer'; 6 + import { MigrationData } from '../../lib/migration/migrationData'; 7 + import '../../css/migration.css'; 8 + 9 + interface MigrationProcessProps { 10 + agent: AtpAgent; 11 + onLogout: () => void; 12 + } 13 + 14 + type MigrationStep = 'account-creation' | 'data-transfer' | 'identity-update' | 'finalization'; 15 + 16 + export default function MigrationProcess({ agent, onLogout }: MigrationProcessProps) { 17 + let navigate = useNavigate(); 18 + const [migrationData, setMigrationData] = useState<MigrationData | null>(null); 19 + const [currentStep, setCurrentStep] = useState<MigrationStep>('account-creation'); 20 + 21 + useEffect(() => { 22 + const handleBeforeUnload = (e: BeforeUnloadEvent) => { 23 + e.preventDefault(); 24 + return ''; 25 + }; 26 + 27 + window.addEventListener('beforeunload', handleBeforeUnload); 28 + 29 + return () => { 30 + window.removeEventListener('beforeunload', handleBeforeUnload); 31 + }; 32 + }, []); 33 + 34 + useEffect(() => { 35 + if (localStorage.getItem('migrationData') == null) { 36 + navigate('/actions'); 37 + } 38 + 39 + const migrationData = JSON.parse(localStorage.getItem('migrationData') || '{}'); 40 + setMigrationData(new MigrationData(migrationData.oldPds, migrationData.newPds, migrationData.inviteCode, migrationData.handle, migrationData.email, migrationData.password)); 41 + }, []); 42 + 43 + return ( 44 + <div className="actions-page"> 45 + <Header agent={agent} onLogout={onLogout} /> 46 + <div className="page-content"> 47 + <h2>Account migration in progress...</h2> 48 + <div className="progress-steps"> 49 + <div className={`progress-step ${currentStep === 'account-creation' ? 'active' : ''}`}> 50 + <div className="step-number">1</div> 51 + <div className="step-text">Creating your new account</div> 52 + </div> 53 + <div className={`progress-step ${currentStep === 'data-transfer' ? 'active' : ''}`}> 54 + <div className="step-number">2</div> 55 + <div className="step-text">Moving your data</div> 56 + </div> 57 + <div className={`progress-step ${currentStep === 'identity-update' ? 'active' : ''}`}> 58 + <div className="step-number">3</div> 59 + <div className="step-text">Declaring your new host</div> 60 + </div> 61 + <div className={`progress-step ${currentStep === 'finalization' ? 'active' : ''}`}> 62 + <div className="step-number">4</div> 63 + <div className="step-text">Finalizing migration</div> 64 + </div> 65 + </div><br /> 66 + <div className="info-message"> 67 + <h3>Details</h3> 68 + <strong>example</strong> 69 + </div> 70 + <div className="warning-section"> 71 + <h3>Important!</h3> 72 + <ul> 73 + <li>Do not close this window or navigate away</li> 74 + <li>Keep your browser open until the process is complete</li> 75 + <li>You will be notified when the migration is finished</li> 76 + </ul> 77 + </div> 78 + </div> 79 + <Footer /> 80 + </div> 81 + ); 82 + }
+138
src/pages/migration/migrationForms.tsx
···
··· 1 + import { useNavigate } from 'react-router-dom'; 2 + import { AtpAgent } from '@atproto/api'; 3 + import { useState, useRef, useEffect } from 'react'; 4 + import Header from '../../components/common/Header'; 5 + import Footer from '../../components/common/Footer'; 6 + import PdsForm from '../../components/migration/PdsForm'; 7 + import AccountDetailsForm from '../../components/migration/AccountDetailsForm'; 8 + import ConfirmationStep from '../../components/migration/ConfirmationStep'; 9 + import { ServerDescription } from '../../lib/migration/serverDescription'; 10 + import { getServerDescription } from '../../lib/migration/pdsValidation'; 11 + import { MigrationData } from '../../lib/migration/migrationData'; 12 + 13 + interface MigrationFormsProps { 14 + agent: AtpAgent; 15 + onLogout: () => void; 16 + } 17 + 18 + export default function MigrationForms({ agent, onLogout }: MigrationFormsProps) { 19 + const navigate = useNavigate(); 20 + const [currentStep, setCurrentStep] = useState<'pds' | 'account' | 'confirmation'>('pds'); 21 + const [pdsDetails, setPdsDetails] = useState<{ pds: string; inviteCode: string; serverDescription: ServerDescription } | null>(null); 22 + const [accountDetails, setAccountDetails] = useState<{ handle: string; email: string; password: string } | null>(null); 23 + const [currentServerDescription, setCurrentServerDescription] = useState<ServerDescription | null>(null); 24 + const accountSectionRef = useRef<HTMLDivElement>(null); 25 + const confirmationSectionRef = useRef<HTMLDivElement>(null); 26 + 27 + useEffect(() => { 28 + if (currentStep === 'account' && accountSectionRef.current) { 29 + accountSectionRef.current.scrollIntoView({ behavior: 'smooth' }); 30 + } else if (currentStep === 'confirmation' && confirmationSectionRef.current) { 31 + confirmationSectionRef.current.scrollIntoView({ behavior: 'smooth' }); 32 + } 33 + }, [currentStep]); 34 + 35 + // Fetch current PDS server description when component mounts 36 + useEffect(() => { 37 + const fetchCurrentServerDescription = async () => { 38 + try { 39 + const currentPds = new URL(agent.serviceUrl).hostname; 40 + 41 + // If the current PDS is a Bluesky network server, use bsky.social's description 42 + if (currentPds.endsWith('.bsky.network')) { 43 + const description = await getServerDescription('bsky.social'); 44 + setCurrentServerDescription(description); 45 + } else { 46 + const description = await getServerDescription(currentPds); 47 + setCurrentServerDescription(description); 48 + } 49 + } catch (e) { 50 + console.error('Failed to fetch current server description:', e); 51 + } 52 + }; 53 + fetchCurrentServerDescription(); 54 + }, [agent.serviceUrl]); 55 + 56 + const handlePdsSubmit = (pds: string, inviteCode: string, serverDescription: ServerDescription) => { 57 + setPdsDetails({ pds, inviteCode, serverDescription }); 58 + setCurrentStep('account'); 59 + }; 60 + 61 + const handleAccountSubmit = (handle: string, email: string, password: string) => { 62 + setAccountDetails({ handle, email, password }); 63 + setCurrentStep('confirmation'); 64 + }; 65 + 66 + const handleBack = () => { 67 + if (currentStep === 'confirmation') { 68 + setCurrentStep('account'); 69 + } else if (currentStep === 'account') { 70 + setCurrentStep('pds'); 71 + } else { 72 + navigate('/migration'); 73 + } 74 + }; 75 + 76 + const handleConfirm = () => { 77 + if (pdsDetails && accountDetails) { 78 + const migrationData = new MigrationData(agent.serviceUrl.hostname, pdsDetails.pds, pdsDetails.inviteCode, accountDetails.handle, accountDetails.email, accountDetails.password); 79 + console.log('Migration data:', migrationData); 80 + localStorage.setItem('migrationData', JSON.stringify(migrationData)); 81 + } else { 82 + console.error('Migration data is not complete'); 83 + return; 84 + } 85 + navigate('/migration/process'); 86 + }; 87 + 88 + return ( 89 + <div className="actions-page"> 90 + <Header agent={agent} onLogout={onLogout} /> 91 + 92 + <div className="actions-container"> 93 + <div className="page-content"> 94 + <h2>Migrate your account</h2> 95 + 96 + <div className={currentStep !== 'pds' ? 'form-section completed' : 'form-section'}> 97 + <PdsForm 98 + agent={agent} 99 + onSubmit={handlePdsSubmit} 100 + onBack={handleBack} 101 + /> 102 + </div> 103 + 104 + {currentStep !== 'pds' && pdsDetails && currentServerDescription && ( 105 + <div className={currentStep === 'account' ? 'form-section' : 'form-section completed'} ref={accountSectionRef}> 106 + <AccountDetailsForm 107 + currentHandle={agent.session?.handle || ''} 108 + pds={pdsDetails.pds} 109 + inviteCode={pdsDetails.inviteCode} 110 + serverDescription={currentServerDescription} 111 + newServerDescription={pdsDetails.serverDescription} 112 + onSubmit={handleAccountSubmit} 113 + onBack={handleBack} 114 + /> 115 + </div> 116 + )} 117 + 118 + {currentStep === 'confirmation' && pdsDetails && accountDetails && ( 119 + <div className="form-section" ref={confirmationSectionRef}> 120 + <ConfirmationStep 121 + handle={accountDetails.handle} 122 + email={accountDetails.email} 123 + password={accountDetails.password} 124 + pds={pdsDetails.pds} 125 + currentHandle={agent.session?.handle || ''} 126 + currentPds={new URL(agent.serviceUrl).hostname} 127 + onBack={handleBack} 128 + onConfirm={handleConfirm} 129 + /> 130 + </div> 131 + )} 132 + </div> 133 + </div> 134 + 135 + <Footer /> 136 + </div> 137 + ); 138 + }
+53
src/pages/recoveryKey/RecoveryKeyProcess.tsx
···
··· 1 + import { useNavigate } from 'react-router-dom'; 2 + import { useEffect } from 'react'; 3 + import { AtpAgent } from '@atproto/api'; 4 + import Footer from '../../components/common/Footer'; 5 + import Header from '../../components/common/Header'; 6 + 7 + interface RecoveryKeyProcessProps { 8 + agent: AtpAgent; 9 + onLogout: () => void; 10 + } 11 + 12 + export default function RecoveryKeyProcess({ agent, onLogout }: RecoveryKeyProcessProps) { 13 + const navigate = useNavigate(); 14 + 15 + // Add warning when trying to close or navigate away 16 + useEffect(() => { 17 + const handleBeforeUnload = (e: BeforeUnloadEvent) => { 18 + e.preventDefault(); 19 + return ''; 20 + }; 21 + 22 + window.addEventListener('beforeunload', handleBeforeUnload); 23 + 24 + return () => { 25 + window.removeEventListener('beforeunload', handleBeforeUnload); 26 + }; 27 + }, []); 28 + 29 + return ( 30 + <div className="actions-page"> 31 + <Header agent={agent} onLogout={onLogout} /> 32 + 33 + <div className="actions-container"> 34 + <div className="page-content"> 35 + <h2>Add Recovery Key</h2> 36 + <p>This page will guide you through the process of adding a recovery key to your account.</p> 37 + <div className="warning-section"> 38 + <h3>This is not implemented yet!</h3> 39 + </div> 40 + <div className="button-container"> 41 + <button 42 + className="back-button" 43 + onClick={() => navigate('/recovery-key')} 44 + > 45 + โ† Go back 46 + </button> 47 + </div> 48 + </div> 49 + </div> 50 + <Footer /> 51 + </div> 52 + ); 53 + }
+66
src/pages/recoveryKey/recovery.tsx
···
··· 1 + import { useNavigate } from 'react-router-dom'; 2 + import { AtpAgent } from '@atproto/api'; 3 + import Footer from '../../components/common/Footer'; 4 + import Header from '../../components/common/Header'; 5 + 6 + interface RecoveryKeyProps { 7 + agent: AtpAgent; 8 + onLogout: () => void; 9 + } 10 + 11 + export default function RecoveryKey({ agent, onLogout }: RecoveryKeyProps) { 12 + const navigate = useNavigate(); 13 + 14 + return ( 15 + <div className="actions-page"> 16 + <Header agent={agent} onLogout={onLogout} /> 17 + 18 + <div className="actions-container"> 19 + <div className="page-content"> 20 + <h2>Add a recovery key</h2> 21 + <p>A recovery key (known as a <b>rotation key</b> in the AT Protocol) is a cryptographic key associated with your account that allows you to modify your account's core identity.</p> 22 + 23 + <h3>How rotation keys work</h3> 24 + <p>In the AT Protocol, your account is identified using a DID (<b>Decentralized Identifier</b>), with most accounts on the protocol using a variant of it developed specifically for the protocol named PLC. The account's core information (such as your handle and data server on the network) is stored in the account's DID document.</p> 25 + <p>To change this document (an event known as a <b>PLC operation</b>), you use a rotation key to confirm that you are the owner of the account and that you are authorized to make the changes. For example, when changing your handle, your data server (also known as a PDS) will use its own rotation key to change the document, allowing the user to change their handle without needing to provide their own key.</p> 26 + <h3>Why should I add another key?</h3> 27 + <p>Adding a rotation key allows you to regain control of your account if it is compromised. It also allows you to move your account to a new data server, even if the current server is down.</p> 28 + <div className="warning-section"> 29 + <h3>โš ๏ธ Read Before You Continue โš ๏ธ</h3> 30 + <ul> 31 + <li>You will need a to add a recovery key. Tokens are sent to the email address associated with your account.</li> 32 + <li>While we do generate a key for you, we will not store it. Please save it in a secure location.</li> 33 + <li>Keep your recovery key private. Anyone with access to it could potentially take control of your account.</li> 34 + <li>If you're using a third-party PDS, it must be able to send emails or you will not be able to use this tool to add a recovery key.</li> 35 + </ul> 36 + </div> 37 + <div className="docs-section"> 38 + <h3>Additional Resources</h3> 39 + <p>For the technically inclined, here are some additional resources for how rotation keys work:</p> 40 + <ul> 41 + <li><a href="https://atproto.com/guides/identity" target="_blank" rel="noopener noreferrer">AT Protocol's developer documentation on identity</a></li> 42 + <li><a href="https://whtwnd.com/did:plc:xz3euvkhf44iadavovbsmqoo/3laimapx6ks2b" target="_blank" rel="noopener noreferrer">Guide to adding a recovery key using the command line</a></li> 43 + <li><a href="https://whtwnd.com/did:plc:44ybard66vv44zksje25o7dz/3lj7jmt2ct72r" target="_blank" rel="noopener noreferrer">More in-depth guide to adding a recovery key</a></li> 44 + </ul> 45 + </div> 46 + 47 + <div className="button-container"> 48 + <button 49 + className="back-button" 50 + onClick={() => navigate('/actions')} 51 + > 52 + โ† Go back 53 + </button> 54 + <button 55 + className="continue-button" 56 + onClick={() => navigate('/recovery-key/process')} 57 + > 58 + Continue โ†’ 59 + </button> 60 + </div> 61 + </div> 62 + </div> 63 + <Footer /> 64 + </div> 65 + ); 66 + }
+119
src/routes.tsx
···
··· 1 + import { useEffect } from 'react' 2 + import { Routes, Route, Navigate, useLocation } from 'react-router-dom' 3 + import { AtpAgent } from '@atproto/api' 4 + 5 + import NetworkWarning from './components/common/NetworkWarning' 6 + import Login from './pages/Login' 7 + import Actions from './pages/Actions' 8 + import Migration from './pages/migration/Migration' 9 + import MigrationForms from './pages/migration/MigrationForms' 10 + import MigrationProcess from './pages/migration/MigrationProcess' 11 + import RecoveryKey from './pages/recoveryKey/Recovery' 12 + import RecoveryKeyProcess from './pages/recoveryKey/RecoveryKeyProcess' 13 + import ErrorPage from './pages/Error' 14 + 15 + export function AppRoutes({ agent, onLogout, handleLogin }: { 16 + agent: AtpAgent | null; 17 + onLogout: () => void; 18 + handleLogin: (agent: AtpAgent) => void; 19 + }) { 20 + const location = useLocation(); 21 + 22 + useEffect(() => { 23 + const checkSession = async () => { 24 + if (agent) { 25 + try { 26 + // Try to make a simple API call to verify the session 27 + await agent.getProfile({ actor: agent.session?.handle || '' }); 28 + } catch (err) { 29 + // If the API call fails, the session is likely invalid 30 + console.error('Session check failed: ', err); 31 + onLogout(); 32 + alert('Your session has expired. Please log in again.'); 33 + } 34 + } 35 + }; 36 + 37 + checkSession(); 38 + }, [location.pathname, agent, onLogout]); 39 + 40 + return ( 41 + <> 42 + <NetworkWarning /> 43 + <Routes> 44 + <Route 45 + path="/" 46 + element={ 47 + agent ? ( 48 + <Navigate to="/actions" replace /> 49 + ) : ( 50 + <Login onLogin={handleLogin} /> 51 + ) 52 + } 53 + /> 54 + <Route 55 + path="/actions" 56 + element={ 57 + agent ? ( 58 + <Actions agent={agent} onLogout={onLogout} /> 59 + ) : ( 60 + <Navigate to="/" replace /> 61 + ) 62 + } 63 + /> 64 + <Route 65 + path="/migration" 66 + element={ 67 + agent ? ( 68 + <Migration agent={agent} onLogout={onLogout} /> 69 + ) : ( 70 + <Navigate to="/" replace /> 71 + ) 72 + } 73 + /> 74 + <Route 75 + path="/migration/registration" 76 + element={ 77 + agent ? ( 78 + <MigrationForms agent={agent} onLogout={onLogout} /> 79 + ) : ( 80 + <Navigate to="/" replace /> 81 + ) 82 + } 83 + /> 84 + <Route 85 + path="/recovery-key" 86 + element={ 87 + agent ? ( 88 + <RecoveryKey agent={agent} onLogout={onLogout} /> 89 + ) : ( 90 + <Navigate to="/" replace /> 91 + ) 92 + } 93 + /> 94 + <Route 95 + path="/recovery-key/process" 96 + element={ 97 + agent ? ( 98 + <RecoveryKeyProcess agent={agent} onLogout={onLogout} /> 99 + ) : ( 100 + <Navigate to="/" replace /> 101 + ) 102 + } 103 + /> 104 + <Route 105 + path="/migration/process" 106 + element={ 107 + agent ? ( 108 + <MigrationProcess agent={agent} onLogout={onLogout} /> 109 + ) : ( 110 + <Navigate to="/" replace /> 111 + ) 112 + } 113 + /> 114 + {/* Catch-all route for 404s */} 115 + <Route path="*" element={<ErrorPage />} /> 116 + </Routes> 117 + </> 118 + ); 119 + }
-738
src/styles/App.css
··· 1 - /* Base styles */ 2 - :root { 3 - --primary-color: #4f46e5; 4 - --primary-hover: #4338ca; 5 - --text-color: #1f2937; 6 - --text-light: #6b7280; 7 - --bg-color: #f3f4f6; 8 - --white: #ffffff; 9 - --error-color: #ef4444; 10 - --border-color: #e5e7eb; 11 - --input-bg: #ffffff; 12 - } 13 - 14 - @media (prefers-color-scheme: dark) { 15 - :root { 16 - --primary-color: #6366f1; 17 - --primary-hover: #4f46e5; 18 - --text-color: #f3f4f6; 19 - --text-light: #9ca3af; 20 - --bg-color: #111827; 21 - --white: #1f2937; 22 - --error-color: #f87171; 23 - --border-color: #374151; 24 - --input-bg: #1f2937; 25 - } 26 - } 27 - 28 - body { 29 - margin: 0; 30 - font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; 31 - background-color: var(--bg-color); 32 - color: var(--text-color); 33 - min-height: 100vh; 34 - display: flex; 35 - flex-direction: column; 36 - } 37 - 38 - /* Login page styles */ 39 - .login-container { 40 - flex: 1; 41 - display: flex; 42 - align-items: center; 43 - justify-content: center; 44 - padding: 1rem; 45 - } 46 - 47 - .login-card { 48 - max-width: 28rem; 49 - width: 100%; 50 - padding: 2rem; 51 - padding-top: 0; 52 - background-color: var(--white); 53 - border-radius: 0.5rem; 54 - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 55 - } 56 - 57 - .login-title { 58 - padding-top: 0; 59 - text-align: center; 60 - font-size: 1.875rem; 61 - font-weight: 800; 62 - color: var(--text-color); 63 - } 64 - 65 - .warning-message { 66 - margin: 1rem 0; 67 - padding: 0.75rem; 68 - background-color: #fef3c7; 69 - border: 1px solid #fbbf24; 70 - border-radius: 0.375rem; 71 - color: #92400e; 72 - font-size: 0.875rem; 73 - text-align: center; 74 - } 75 - 76 - .login-form { 77 - margin-top: 2rem; 78 - padding: 0 1rem; 79 - } 80 - 81 - .form-group { 82 - margin-bottom: 1rem; 83 - } 84 - 85 - .form-group label { 86 - display: block; 87 - margin-bottom: 0.5rem; 88 - color: var(--text-color); 89 - font-weight: 500; 90 - } 91 - 92 - .handle-input-container { 93 - display: flex; 94 - align-items: stretch; 95 - gap: 0; 96 - background-color: var(--input-bg); 97 - border: 1px solid var(--border-color); 98 - border-radius: 0.375rem; 99 - } 100 - 101 - .handle-input-container .form-input { 102 - border: none; 103 - padding-right: 0; 104 - flex: 1; 105 - border-radius: 0.375rem 0 0 0.375rem; 106 - } 107 - 108 - .handle-input-container .form-input:focus { 109 - box-shadow: none; 110 - } 111 - 112 - .handle-domain { 113 - color: var(--text-light); 114 - font-size: 0.875rem; 115 - white-space: nowrap; 116 - background-color: var(--bg-color); 117 - padding: 0 0.75rem; 118 - border-radius: 0 0.375rem 0.375rem 0; 119 - display: flex; 120 - align-items: center; 121 - } 122 - 123 - .form-input { 124 - width: 100%; 125 - padding: 0.75rem 1rem; 126 - border: 1px solid var(--border-color); 127 - border-radius: 0.375rem; 128 - font-size: 0.875rem; 129 - color: var(--text-color); 130 - background-color: var(--input-bg); 131 - box-sizing: border-box; 132 - } 133 - 134 - .form-input:focus { 135 - outline: none; 136 - border-color: var(--primary-color); 137 - box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1); 138 - } 139 - 140 - .form-input::placeholder { 141 - color: var(--text-light); 142 - } 143 - 144 - .error-message { 145 - color: var(--error-color); 146 - font-size: 0.875rem; 147 - text-align: center; 148 - margin: 0.5rem 0; 149 - } 150 - 151 - .success-message { 152 - color: #059669; 153 - font-size: 0.875rem; 154 - margin: 0.5rem 0; 155 - display: flex; 156 - align-items: center; 157 - gap: 0.5rem; 158 - } 159 - 160 - .submit-button { 161 - width: 100%; 162 - padding: 0.75rem; 163 - background-color: var(--primary-color); 164 - color: var(--white); 165 - border: none; 166 - border-radius: 0.375rem; 167 - font-size: 0.875rem; 168 - font-weight: 500; 169 - cursor: pointer; 170 - transition: background-color 0.2s; 171 - margin-top: 1rem; 172 - } 173 - 174 - .submit-button:hover { 175 - background-color: var(--primary-hover); 176 - } 177 - 178 - /* Header styles */ 179 - .app-header { 180 - display: flex; 181 - flex-wrap: wrap; 182 - align-items: center; 183 - justify-content: space-between; 184 - gap: 1rem; 185 - padding: 1rem; 186 - background-color: var(--white); 187 - border-radius: 0.5rem; 188 - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 189 - margin: 1rem 0 0 0; 190 - position: relative; 191 - z-index: 10; 192 - } 193 - 194 - .app-title { 195 - font-size: 1.25rem; 196 - font-weight: 700; 197 - color: var(--text-color); 198 - margin: 0; 199 - white-space: nowrap; 200 - overflow: hidden; 201 - text-overflow: ellipsis; 202 - max-width: 200px; 203 - } 204 - 205 - .user-info { 206 - display: flex; 207 - align-items: center; 208 - gap: 0.75rem; 209 - flex-wrap: wrap; 210 - } 211 - 212 - .user-avatar { 213 - width: 2rem; 214 - height: 2rem; 215 - border-radius: 50%; 216 - object-fit: cover; 217 - flex-shrink: 0; 218 - } 219 - 220 - .user-handle { 221 - font-size: 0.875rem; 222 - color: var(--text-color); 223 - white-space: nowrap; 224 - overflow: hidden; 225 - text-overflow: ellipsis; 226 - max-width: 150px; 227 - } 228 - 229 - .logout-button { 230 - background-color: #f44336; 231 - color: white; 232 - border: none; 233 - padding: 8px 16px; 234 - border-radius: 4px; 235 - cursor: pointer; 236 - font-size: 14px; 237 - transition: background-color 0.2s; 238 - white-space: nowrap; 239 - flex-shrink: 0; 240 - } 241 - 242 - @media (max-width: 480px) { 243 - .app-header { 244 - padding: 0.75rem; 245 - gap: 0.5rem; 246 - } 247 - 248 - .app-title { 249 - font-size: 1.125rem; 250 - max-width: 150px; 251 - } 252 - 253 - .user-handle { 254 - max-width: 100px; 255 - } 256 - 257 - .logout-button { 258 - padding: 6px 12px; 259 - font-size: 13px; 260 - } 261 - } 262 - 263 - /* Actions page styles */ 264 - .actions-page { 265 - display: flex; 266 - flex-direction: column; 267 - min-height: 100vh; 268 - max-width: 800px; 269 - margin: 0 auto; 270 - padding: 0 20px; 271 - gap: 1rem; 272 - } 273 - 274 - .actions-container { 275 - padding: 0; 276 - max-width: 800px; 277 - width: 100%; 278 - margin: 0 auto; 279 - } 280 - 281 - .actions-list { 282 - display: flex; 283 - flex-direction: column; 284 - gap: 1rem; 285 - margin-top: 1rem; 286 - } 287 - 288 - .action-item { 289 - display: flex; 290 - align-items: center; 291 - gap: 1rem; 292 - padding: 1rem; 293 - background-color: var(--white); 294 - border-radius: 0.5rem; 295 - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 296 - cursor: pointer; 297 - transition: transform 0.2s, box-shadow 0.2s; 298 - border: none; 299 - width: 100%; 300 - text-align: left; 301 - } 302 - 303 - .action-item:hover { 304 - transform: translateY(-2px); 305 - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 306 - } 307 - 308 - .action-icon { 309 - width: 2.5rem; 310 - height: 2.5rem; 311 - display: flex; 312 - align-items: center; 313 - justify-content: center; 314 - color: var(--primary-color); 315 - background-color: var(--bg-color); 316 - border-radius: 0.5rem; 317 - flex-shrink: 0; 318 - } 319 - 320 - .action-content { 321 - flex: 1; 322 - text-align: left; 323 - } 324 - 325 - .action-title { 326 - font-size: 1rem; 327 - font-weight: 600; 328 - color: var(--text-color); 329 - margin-bottom: 0.25rem; 330 - text-align: left; 331 - } 332 - 333 - .action-subtitle { 334 - font-size: 0.875rem; 335 - color: var(--text-light); 336 - text-align: left; 337 - } 338 - 339 - /* User info section */ 340 - .user-info-section { 341 - background-color: var(--white); 342 - border-radius: 0.5rem; 343 - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 344 - overflow: hidden; 345 - margin-top: 1rem; 346 - } 347 - 348 - .user-info-summary { 349 - padding: 1rem; 350 - cursor: pointer; 351 - display: flex; 352 - align-items: center; 353 - justify-content: space-between; 354 - list-style: none; 355 - font-weight: 600; 356 - color: var(--text-color); 357 - } 358 - 359 - .user-info-summary::after { 360 - content: "โ–ผ"; 361 - color: var(--primary-color); 362 - transition: transform 0.2s; 363 - } 364 - 365 - .user-info-section[open] .user-info-summary::after { 366 - transform: rotate(180deg); 367 - } 368 - 369 - .user-info-content { 370 - padding: 1.5rem; 371 - border-top: 1px solid var(--border-color); 372 - } 373 - 374 - .user-info-content section { 375 - margin-bottom: 2rem; 376 - } 377 - 378 - .user-info-content section:last-child { 379 - margin-bottom: 0; 380 - } 381 - 382 - .user-info-content h2 { 383 - font-size: 1.125rem; 384 - font-weight: 600; 385 - color: var(--text-color); 386 - margin-bottom: 1rem; 387 - } 388 - 389 - .user-info-content dl { 390 - display: grid; 391 - grid-template-columns: max-content 1fr; 392 - gap: 0.75rem 1rem; 393 - margin: 0; 394 - } 395 - 396 - .user-info-content dt { 397 - font-weight: 500; 398 - color: var(--text-light); 399 - } 400 - 401 - .user-info-content dd { 402 - margin: 0; 403 - color: var(--text-color); 404 - word-break: break-all; 405 - } 406 - 407 - .did-document { 408 - background-color: var(--bg-color); 409 - padding: 1rem; 410 - border-radius: 0.375rem; 411 - overflow-x: auto; 412 - margin: 0; 413 - } 414 - 415 - .did-document code { 416 - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 417 - font-size: 0.875rem; 418 - color: var(--text-color); 419 - white-space: pre; 420 - } 421 - 422 - /* Loading state */ 423 - .loading-container { 424 - flex: 1; 425 - display: flex; 426 - align-items: center; 427 - justify-content: center; 428 - min-height: 100vh; 429 - padding: 1rem; 430 - } 431 - 432 - .loading-text { 433 - font-size: 1.25rem; 434 - color: var(--text-color); 435 - background-color: var(--white); 436 - padding: 1.5rem 2rem; 437 - border-radius: 0.5rem; 438 - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 439 - display: flex; 440 - align-items: center; 441 - gap: 0.75rem; 442 - } 443 - 444 - .loading-text::before { 445 - content: ""; 446 - width: 1.5rem; 447 - height: 1.5rem; 448 - border: 2px solid var(--primary-color); 449 - border-right-color: transparent; 450 - border-radius: 50%; 451 - animation: spin 1s linear infinite; 452 - } 453 - 454 - @keyframes spin { 455 - to { 456 - transform: rotate(360deg); 457 - } 458 - } 459 - 460 - /* Footer styles */ 461 - .footer { 462 - text-align: center; 463 - padding: 1rem; 464 - color: var(--text-light); 465 - font-size: 0.875rem; 466 - margin: 1rem 0; 467 - } 468 - 469 - .footer-link { 470 - color: var(--primary-color); 471 - text-decoration: none; 472 - transition: color 0.2s; 473 - } 474 - 475 - .footer-link:hover { 476 - color: var(--primary-hover); 477 - } 478 - 479 - .footer-link strong { 480 - font-weight: 600; 481 - } 482 - 483 - .loading-message { 484 - margin: 10px 0; 485 - padding: 10px; 486 - background-color: var(--bg-color); 487 - border-radius: 4px; 488 - color: var(--text-color); 489 - font-size: 0.9em; 490 - text-align: center; 491 - } 492 - 493 - .form-input:disabled { 494 - background-color: var(--bg-color); 495 - border-color: var(--border-color); 496 - color: var(--text-light); 497 - cursor: not-allowed; 498 - opacity: 0.8; 499 - } 500 - 501 - .submit-button:disabled { 502 - background-color: var(--text-light); 503 - cursor: not-allowed; 504 - opacity: 0.8; 505 - } 506 - 507 - .page-content { 508 - background-color: var(--white); 509 - border-radius: 0.5rem; 510 - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 511 - padding: 2rem; 512 - padding-top: 1rem; 513 - margin-top: 1rem; 514 - } 515 - 516 - .page-content h2 { 517 - font-size: 1.5rem; 518 - font-weight: 600; 519 - color: var(--text-color); 520 - margin-bottom: 1rem; 521 - } 522 - 523 - .page-content p { 524 - color: var(--text-color); 525 - margin-bottom: 1rem; 526 - } 527 - 528 - .page-content ul { 529 - list-style-type: disc; 530 - margin-left: 1.5rem; 531 - margin-bottom: 1.5rem; 532 - color: var(--text-color); 533 - } 534 - 535 - .page-content li { 536 - margin-bottom: 0.5rem; 537 - } 538 - 539 - .back-button { 540 - background-color: var(--primary-color); 541 - color: var(--white); 542 - border: none; 543 - padding: 0.75rem 1.5rem; 544 - border-radius: 0.375rem; 545 - font-size: 0.875rem; 546 - font-weight: 500; 547 - cursor: pointer; 548 - transition: background-color 0.2s; 549 - display: inline-flex; 550 - align-items: center; 551 - gap: 0.5rem; 552 - } 553 - 554 - .button-container { 555 - display: flex; 556 - justify-content: space-between; 557 - gap: 1rem; 558 - margin-top: 2rem; 559 - } 560 - 561 - .back-button { 562 - background-color: var(--text-light); 563 - color: var(--white); 564 - border: none; 565 - padding: 0.75rem 1.5rem; 566 - border-radius: 0.375rem; 567 - font-size: 0.875rem; 568 - font-weight: 500; 569 - cursor: pointer; 570 - transition: background-color 0.2s; 571 - display: inline-flex; 572 - align-items: center; 573 - gap: 0.5rem; 574 - } 575 - 576 - .back-button:hover { 577 - background-color: #c7c7c7; 578 - } 579 - 580 - .continue-button { 581 - background-color: var(--primary-color); 582 - color: var(--white); 583 - border: none; 584 - padding: 0.75rem 1.5rem; 585 - border-radius: 0.375rem; 586 - font-size: 0.875rem; 587 - font-weight: 500; 588 - cursor: pointer; 589 - transition: background-color 0.2s; 590 - display: inline-flex; 591 - align-items: center; 592 - gap: 0.5rem; 593 - margin-left: auto; 594 - } 595 - 596 - .continue-button:hover { 597 - background-color: var(--primary-hover); 598 - } 599 - 600 - .page-content h3 { 601 - font-size: 1.25rem; 602 - font-weight: 600; 603 - color: var(--text-color); 604 - margin: 1.5rem 0 1rem 0; 605 - } 606 - 607 - .warning-section { 608 - background-color: #fef3c7; 609 - border: 1px solid #fbbf24; 610 - border-radius: 0.5rem; 611 - padding: 1.5rem; 612 - margin: 1.5rem 0; 613 - } 614 - 615 - .warning-section h3 { 616 - color: #92400e; 617 - margin-top: 0; 618 - } 619 - 620 - .warning-section ul { 621 - margin-bottom: 0; 622 - padding-left: 0; 623 - } 624 - 625 - .warning-section li { 626 - color: #92400e; 627 - } 628 - 629 - .warning-section b { 630 - color: #78350f; 631 - } 632 - 633 - .docs-section { 634 - background-color: var(--bg-color); 635 - border-radius: 0.5rem; 636 - padding: 1.5rem; 637 - margin: 1.5rem 0; 638 - } 639 - 640 - .docs-section h3 { 641 - margin-top: 0; 642 - } 643 - 644 - .docs-section ul { 645 - margin-bottom: 0; 646 - padding-left: 0; 647 - } 648 - 649 - .docs-section a { 650 - color: var(--primary-color); 651 - text-decoration: none; 652 - transition: color 0.2s; 653 - display: inline-flex; 654 - align-items: center; 655 - gap: 0.5rem; 656 - } 657 - 658 - .docs-section a:hover { 659 - color: var(--primary-hover); 660 - } 661 - 662 - .docs-section a::after { 663 - content: "โ†’"; 664 - transition: transform 0.2s; 665 - } 666 - 667 - .docs-section a:hover::after { 668 - transform: translateX(4px); 669 - } 670 - 671 - .network-warning { 672 - position: fixed; 673 - top: 0; 674 - left: 0; 675 - right: 0; 676 - bottom: 0; 677 - background-color: rgba(0, 0, 0, 0.8); 678 - z-index: 1000; 679 - display: flex; 680 - align-items: center; 681 - justify-content: center; 682 - } 683 - 684 - .network-warning-content { 685 - max-width: 800px; 686 - padding: 2rem; 687 - display: flex; 688 - align-items: center; 689 - gap: 0.75rem; 690 - color: #92400e; 691 - font-size: 1.125rem; 692 - text-align: center; 693 - background-color: rgba(255, 255, 255, 0.9); 694 - border-radius: 0.5rem; 695 - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 696 - } 697 - 698 - .network-warning-icon { 699 - font-size: 1.5rem; 700 - flex-shrink: 0; 701 - } 702 - 703 - .form-section { 704 - background: var(--white); 705 - border-radius: 8px; 706 - padding: 2rem; 707 - margin-bottom: 2rem; 708 - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 709 - transition: all 0.3s ease; 710 - border: 1px solid var(--border-color); 711 - } 712 - 713 - .form-section.completed { 714 - opacity: 0.6; 715 - pointer-events: none; 716 - background: var(--bg-color); 717 - } 718 - 719 - .form-section h3 { 720 - margin-top: 0; 721 - margin-bottom: 1.5rem; 722 - color: var(--text-color); 723 - font-size: 1.25rem; 724 - } 725 - 726 - .info-message { 727 - color: var(--primary-color); 728 - font-size: 0.875rem; 729 - margin-top: 0.5rem; 730 - display: flex; 731 - align-items: center; 732 - gap: 0.5rem; 733 - } 734 - 735 - .info-message::before { 736 - content: "โ„น๏ธ"; 737 - font-size: 1rem; 738 - }
···
+10 -4
tsconfig.app.json
··· 1 { 2 "compilerOptions": { 3 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 - "target": "ES2020", 5 "useDefineForClassFields": true, 6 - "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 "module": "ESNext", 8 "skipLibCheck": true, 9 10 /* Bundler mode */ 11 "moduleResolution": "bundler", 12 "allowImportingTsExtensions": true, 13 "isolatedModules": true, 14 "moduleDetection": "force", 15 "noEmit": true, ··· 20 "noUnusedLocals": true, 21 "noUnusedParameters": true, 22 "noFallthroughCasesInSwitch": true, 23 - "noUncheckedSideEffectImports": true 24 }, 25 - "include": ["src"] 26 }
··· 1 { 2 "compilerOptions": { 3 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 + "target": "ES2023", 5 "useDefineForClassFields": true, 6 + "lib": ["ES2023", "DOM", "DOM.Iterable"], 7 "module": "ESNext", 8 "skipLibCheck": true, 9 10 /* Bundler mode */ 11 "moduleResolution": "bundler", 12 "allowImportingTsExtensions": true, 13 + "resolveJsonModule": true, 14 "isolatedModules": true, 15 "moduleDetection": "force", 16 "noEmit": true, ··· 21 "noUnusedLocals": true, 22 "noUnusedParameters": true, 23 "noFallthroughCasesInSwitch": true, 24 + "noUncheckedSideEffectImports": true, 25 + "forceConsistentCasingInFileNames": true, 26 + 27 + /* Types */ 28 + "types": ["react", "react-dom", "node"] 29 }, 30 + "include": ["src/**/*"], 31 + "references": [{ "path": "./tsconfig.node.json" }] 32 }
+5 -5
tsconfig.node.json
··· 1 { 2 "compilerOptions": { 3 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 - "target": "ES2022", 5 "lib": ["ES2023"], 6 "module": "ESNext", 7 "skipLibCheck": true, 8 9 /* Bundler mode */ 10 - "moduleResolution": "bundler", 11 - "allowImportingTsExtensions": true, 12 "isolatedModules": true, 13 "moduleDetection": "force", 14 - "noEmit": true, 15 16 /* Linting */ 17 "strict": true, 18 "noUnusedLocals": true, 19 "noUnusedParameters": true, 20 "noFallthroughCasesInSwitch": true, 21 - "noUncheckedSideEffectImports": true 22 }, 23 "include": ["vite.config.ts"] 24 }
··· 1 { 2 "compilerOptions": { 3 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 + "target": "ES2023", 5 "lib": ["ES2023"], 6 "module": "ESNext", 7 "skipLibCheck": true, 8 9 /* Bundler mode */ 10 + "moduleResolution": "node", 11 "isolatedModules": true, 12 "moduleDetection": "force", 13 + "composite": true, 14 15 /* Linting */ 16 "strict": true, 17 "noUnusedLocals": true, 18 "noUnusedParameters": true, 19 "noFallthroughCasesInSwitch": true, 20 + "noUncheckedSideEffectImports": true, 21 + "forceConsistentCasingInFileNames": true 22 }, 23 "include": ["vite.config.ts"] 24 }
+37 -3
vite.config.ts
··· 1 - import { defineConfig } from 'vite' 2 - import react from '@vitejs/plugin-react' 3 4 // https://vitejs.dev/config/ 5 export default defineConfig({ 6 - plugins: [react()], 7 build: { 8 outDir: 'dist', 9 sourcemap: true, 10 }, 11 server: { 12 port: 3000, 13 }, 14 })
··· 1 + import { defineConfig } from 'vite'; 2 + import react from '@vitejs/plugin-react'; 3 + import { visualizer } from 'rollup-plugin-visualizer'; 4 5 // https://vitejs.dev/config/ 6 export default defineConfig({ 7 + plugins: [ 8 + react(), 9 + ...(process.env.ANALYZE ? [visualizer({ 10 + open: true, 11 + filename: 'dist/bundle-analysis.html', 12 + gzipSize: true 13 + })] : []) 14 + ], 15 build: { 16 outDir: 'dist', 17 + emptyOutDir: true, 18 sourcemap: true, 19 + minify: 'terser', 20 + terserOptions: { 21 + compress: { 22 + drop_console: true, 23 + drop_debugger: true 24 + } 25 + }, 26 + rollupOptions: { 27 + external: [ 28 + 'multiformats', 29 + 'multiformats/basics', 30 + 'multiformats/cid', 31 + 'multiformats/hashes/sha2', 32 + 'multiformats/hashes/sha3', 33 + 'iso-datestring-validator', 34 + 'uint8arrays' 35 + ], 36 + output: { 37 + manualChunks: { 38 + 'vendor-react': ['react', 'react-dom', 'react-router-dom', 'scheduler', 'react-dom/client'], 39 + 'vendor-atproto': ['@atproto/api', '@atproto/crypto'] 40 + } 41 + } 42 + } 43 }, 44 server: { 45 port: 3000, 46 }, 47 + publicDir: 'public' 48 })