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 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. 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 3 4 4 [live version available here](https://atproto-migrator.pages.dev) 5 5 6 - ## install 6 + ## run for developing 7 7 8 8 ```bash 9 9 npm install ··· 15 15 ```bash 16 16 npm run build 17 17 ``` 18 + 19 + if you type ```ANALYZE=1``` you will receive a bundle analysis, useful for optimizing 18 20 19 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 2 <html lang="en"> 3 3 <head> 4 4 <meta charset="UTF-8" /> 5 - <link rel="icon" type="image/svg+xml" href="/vite.svg" /> 5 + <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 6 6 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 7 <title>ATproto Migrator</title> 8 8 </head> ··· 41 41 <div id="root"></div> 42 42 <script type="module" src="/src/main.tsx"></script> 43 43 </body> 44 - </html> 44 + </html>
+893 -1309
package-lock.json
··· 1 1 { 2 2 "name": "atproto-migration", 3 - "version": "0.0.0", 3 + "version": "0.1.0", 4 4 "lockfileVersion": 3, 5 5 "requires": true, 6 6 "packages": { 7 7 "": { 8 8 "name": "atproto-migration", 9 - "version": "0.0.0", 9 + "version": "0.1.0", 10 10 "dependencies": { 11 11 "@atproto/api": "^0.15.5", 12 + "@atproto/crypto": "^0.4.4", 13 + "multiformats": "^13.3.6", 12 14 "react": "^19.0.0", 13 15 "react-dom": "^19.0.0", 14 - "react-router-dom": "^7.5.3" 16 + "react-router-dom": "^7.5.3", 17 + "uint8arrays": "^5.1.0" 15 18 }, 16 19 "devDependencies": { 20 + "@eslint/eslintrc": "^3.3.1", 17 21 "@eslint/js": "^9.22.0", 18 - "@types/react": "^19.0.10", 19 - "@types/react-dom": "^19.0.4", 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", 20 28 "@vitejs/plugin-react": "^4.3.4", 21 29 "eslint": "^9.22.0", 22 30 "eslint-plugin-react-hooks": "^5.2.0", 23 31 "eslint-plugin-react-refresh": "^0.4.19", 24 32 "globals": "^16.0.0", 33 + "jiti": "^2.4.2", 34 + "rollup-plugin-visualizer": "^6.0.1", 35 + "terser": "^5.40.0", 25 36 "typescript": "~5.7.2", 26 37 "typescript-eslint": "^8.26.1", 27 38 "vite": "^6.3.1" ··· 42 53 } 43 54 }, 44 55 "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==", 56 + "version": "0.15.10", 57 + "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.15.10.tgz", 58 + "integrity": "sha512-/PsvYoYMA6VGAbMEOU2rOuaNQHkWPU6CVQAUDK2XRlIgFO2d21KEjZsZ4Z3lELvZlcw25fuMp7gLgFRijpk78w==", 48 59 "license": "MIT", 49 60 "dependencies": { 50 - "@atproto/common-web": "^0.4.1", 51 - "@atproto/lexicon": "^0.4.10", 61 + "@atproto/common-web": "^0.4.2", 62 + "@atproto/lexicon": "^0.4.11", 52 63 "@atproto/syntax": "^0.4.0", 53 - "@atproto/xrpc": "^0.6.12", 64 + "@atproto/xrpc": "^0.7.0", 54 65 "await-lock": "^2.2.2", 55 66 "multiformats": "^9.9.0", 56 67 "tlds": "^1.234.0", 57 68 "zod": "^3.23.8" 58 69 } 59 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 + }, 60 77 "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==", 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==", 64 81 "license": "MIT", 65 82 "dependencies": { 66 83 "graphemer": "^1.4.0", ··· 69 86 "zod": "^3.23.8" 70 87 } 71 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 + }, 72 133 "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==", 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==", 76 137 "license": "MIT", 77 138 "dependencies": { 78 - "@atproto/common-web": "^0.4.1", 139 + "@atproto/common-web": "^0.4.2", 79 140 "@atproto/syntax": "^0.4.0", 80 141 "iso-datestring-validator": "^2.2.2", 81 142 "multiformats": "^9.9.0", 82 143 "zod": "^3.23.8" 83 144 } 84 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 + }, 85 152 "node_modules/@atproto/syntax": { 86 153 "version": "0.4.0", 87 154 "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.0.tgz", ··· 89 156 "license": "MIT" 90 157 }, 91 158 "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==", 159 + "version": "0.7.0", 160 + "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.0.tgz", 161 + "integrity": "sha512-SfhP9dGx2qclaScFDb58Jnrmim5nk4geZXCqg6sB0I/KZhZEkr9iIx1hLCp+sxkIfEsmEJjeWO4B0rjUIJW5cw==", 95 162 "license": "MIT", 96 163 "dependencies": { 97 - "@atproto/lexicon": "^0.4.10", 164 + "@atproto/lexicon": "^0.4.11", 98 165 "zod": "^3.23.8" 99 166 } 100 167 }, ··· 114 181 } 115 182 }, 116 183 "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==", 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==", 120 187 "dev": true, 121 188 "license": "MIT", 122 189 "engines": { ··· 124 191 } 125 192 }, 126 193 "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==", 194 + "version": "7.27.3", 195 + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.3.tgz", 196 + "integrity": "sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==", 130 197 "dev": true, 131 198 "license": "MIT", 132 199 "dependencies": { 133 200 "@ampproject/remapping": "^2.2.0", 134 201 "@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", 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", 143 210 "convert-source-map": "^2.0.0", 144 211 "debug": "^4.1.0", 145 212 "gensync": "^1.0.0-beta.2", ··· 154 221 "url": "https://opencollective.com/babel" 155 222 } 156 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 + }, 157 234 "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==", 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==", 161 238 "dev": true, 162 239 "license": "MIT", 163 240 "dependencies": { 164 - "@babel/parser": "^7.27.1", 165 - "@babel/types": "^7.27.1", 241 + "@babel/parser": "^7.27.3", 242 + "@babel/types": "^7.27.3", 166 243 "@jridgewell/gen-mapping": "^0.3.5", 167 244 "@jridgewell/trace-mapping": "^0.3.25", 168 245 "jsesc": "^3.0.2" ··· 172 249 } 173 250 }, 174 251 "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==", 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==", 178 255 "dev": true, 179 256 "license": "MIT", 180 257 "dependencies": { 181 - "@babel/compat-data": "^7.27.1", 258 + "@babel/compat-data": "^7.27.2", 182 259 "@babel/helper-validator-option": "^7.27.1", 183 260 "browserslist": "^4.24.0", 184 261 "lru-cache": "^5.1.1", ··· 188 265 "node": ">=6.9.0" 189 266 } 190 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 + }, 191 278 "node_modules/@babel/helper-module-imports": { 192 279 "version": "7.27.1", 193 280 "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", ··· 203 290 } 204 291 }, 205 292 "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==", 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==", 209 296 "dev": true, 210 297 "license": "MIT", 211 298 "dependencies": { 212 299 "@babel/helper-module-imports": "^7.27.1", 213 300 "@babel/helper-validator-identifier": "^7.27.1", 214 - "@babel/traverse": "^7.27.1" 301 + "@babel/traverse": "^7.27.3" 215 302 }, 216 303 "engines": { 217 304 "node": ">=6.9.0" ··· 261 348 } 262 349 }, 263 350 "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==", 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==", 267 354 "dev": true, 268 355 "license": "MIT", 269 356 "dependencies": { 270 - "@babel/template": "^7.27.1", 271 - "@babel/types": "^7.27.1" 357 + "@babel/template": "^7.27.2", 358 + "@babel/types": "^7.27.3" 272 359 }, 273 360 "engines": { 274 361 "node": ">=6.9.0" 275 362 } 276 363 }, 277 364 "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==", 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==", 281 368 "dev": true, 282 369 "license": "MIT", 283 370 "dependencies": { 284 - "@babel/types": "^7.27.1" 371 + "@babel/types": "^7.27.3" 285 372 }, 286 373 "bin": { 287 374 "parser": "bin/babel-parser.js" ··· 323 410 } 324 411 }, 325 412 "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==", 413 + "version": "7.27.2", 414 + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", 415 + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", 329 416 "dev": true, 330 417 "license": "MIT", 331 418 "dependencies": { 332 419 "@babel/code-frame": "^7.27.1", 333 - "@babel/parser": "^7.27.1", 420 + "@babel/parser": "^7.27.2", 334 421 "@babel/types": "^7.27.1" 335 422 }, 336 423 "engines": { ··· 338 425 } 339 426 }, 340 427 "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==", 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==", 344 431 "dev": true, 345 432 "license": "MIT", 346 433 "dependencies": { 347 434 "@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", 435 + "@babel/generator": "^7.27.3", 436 + "@babel/parser": "^7.27.3", 437 + "@babel/template": "^7.27.2", 438 + "@babel/types": "^7.27.3", 352 439 "debug": "^4.3.1", 353 440 "globals": "^11.1.0" 354 441 }, ··· 367 454 } 368 455 }, 369 456 "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==", 457 + "version": "7.27.3", 458 + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", 459 + "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", 373 460 "dev": true, 374 461 "license": "MIT", 375 462 "dependencies": { ··· 381 468 } 382 469 }, 383 470 "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==", 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==", 387 474 "cpu": [ 388 475 "ppc64" 389 476 ], ··· 398 485 } 399 486 }, 400 487 "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==", 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==", 404 491 "cpu": [ 405 492 "arm" 406 493 ], ··· 415 502 } 416 503 }, 417 504 "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==", 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==", 421 508 "cpu": [ 422 509 "arm64" 423 510 ], ··· 432 519 } 433 520 }, 434 521 "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==", 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==", 438 525 "cpu": [ 439 526 "x64" 440 527 ], ··· 449 536 } 450 537 }, 451 538 "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==", 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==", 455 542 "cpu": [ 456 543 "arm64" 457 544 ], ··· 466 553 } 467 554 }, 468 555 "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==", 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==", 472 559 "cpu": [ 473 560 "x64" 474 561 ], ··· 483 570 } 484 571 }, 485 572 "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==", 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==", 489 576 "cpu": [ 490 577 "arm64" 491 578 ], ··· 500 587 } 501 588 }, 502 589 "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==", 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==", 506 593 "cpu": [ 507 594 "x64" 508 595 ], ··· 517 604 } 518 605 }, 519 606 "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==", 607 + "version": "0.25.5", 608 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", 609 + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", 523 610 "cpu": [ 524 611 "arm" 525 612 ], ··· 534 621 } 535 622 }, 536 623 "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==", 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==", 540 627 "cpu": [ 541 628 "arm64" 542 629 ], ··· 551 638 } 552 639 }, 553 640 "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==", 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==", 557 644 "cpu": [ 558 645 "ia32" 559 646 ], ··· 568 655 } 569 656 }, 570 657 "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==", 658 + "version": "0.25.5", 659 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", 660 + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", 574 661 "cpu": [ 575 662 "loong64" 576 663 ], ··· 585 672 } 586 673 }, 587 674 "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==", 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==", 591 678 "cpu": [ 592 679 "mips64el" 593 680 ], ··· 602 689 } 603 690 }, 604 691 "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==", 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==", 608 695 "cpu": [ 609 696 "ppc64" 610 697 ], ··· 619 706 } 620 707 }, 621 708 "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==", 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==", 625 712 "cpu": [ 626 713 "riscv64" 627 714 ], ··· 636 723 } 637 724 }, 638 725 "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==", 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==", 642 729 "cpu": [ 643 730 "s390x" 644 731 ], ··· 653 740 } 654 741 }, 655 742 "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==", 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==", 659 746 "cpu": [ 660 747 "x64" 661 748 ], ··· 670 757 } 671 758 }, 672 759 "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==", 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==", 676 763 "cpu": [ 677 764 "arm64" 678 765 ], ··· 687 774 } 688 775 }, 689 776 "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==", 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==", 693 780 "cpu": [ 694 781 "x64" 695 782 ], ··· 704 791 } 705 792 }, 706 793 "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==", 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==", 710 797 "cpu": [ 711 798 "arm64" 712 799 ], ··· 721 808 } 722 809 }, 723 810 "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==", 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==", 727 814 "cpu": [ 728 815 "x64" 729 816 ], ··· 738 825 } 739 826 }, 740 827 "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==", 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==", 744 831 "cpu": [ 745 832 "x64" 746 833 ], ··· 755 842 } 756 843 }, 757 844 "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==", 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==", 761 848 "cpu": [ 762 849 "arm64" 763 850 ], ··· 772 859 } 773 860 }, 774 861 "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==", 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==", 778 865 "cpu": [ 779 866 "ia32" 780 867 ], ··· 789 876 } 790 877 }, 791 878 "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==", 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==", 795 882 "cpu": [ 796 883 "x64" 797 884 ], ··· 824 911 "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 825 912 } 826 913 }, 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 914 "node_modules/@eslint-community/regexpp": { 841 915 "version": "4.12.1", 842 916 "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", ··· 873 947 } 874 948 }, 875 949 "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==", 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==", 879 953 "dev": true, 880 954 "license": "Apache-2.0", 881 955 "dependencies": { ··· 923 997 } 924 998 }, 925 999 "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==", 1000 + "version": "9.27.0", 1001 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", 1002 + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", 929 1003 "dev": true, 930 1004 "license": "MIT", 931 1005 "engines": { 932 1006 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1007 + }, 1008 + "funding": { 1009 + "url": "https://eslint.org/donate" 933 1010 } 934 1011 }, 935 1012 "node_modules/@eslint/object-schema": { ··· 943 1020 } 944 1021 }, 945 1022 "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==", 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==", 949 1026 "dev": true, 950 1027 "license": "Apache-2.0", 951 1028 "dependencies": { 952 - "@eslint/core": "^0.13.0", 1029 + "@eslint/core": "^0.14.0", 953 1030 "levn": "^0.4.1" 954 1031 }, 955 1032 "engines": { ··· 1009 1086 } 1010 1087 }, 1011 1088 "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==", 1089 + "version": "0.4.3", 1090 + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", 1091 + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", 1015 1092 "dev": true, 1016 1093 "license": "Apache-2.0", 1017 1094 "engines": { ··· 1057 1134 "node": ">=6.0.0" 1058 1135 } 1059 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 + }, 1060 1148 "node_modules/@jridgewell/sourcemap-codec": { 1061 1149 "version": "1.5.0", 1062 1150 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", ··· 1075 1163 "@jridgewell/sourcemap-codec": "^1.4.14" 1076 1164 } 1077 1165 }, 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, 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==", 1083 1170 "license": "MIT", 1084 1171 "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" 1172 + "@noble/hashes": "1.8.0" 1173 + }, 1174 + "engines": { 1175 + "node": "^14.21.3 || >=16" 1095 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", 1096 1186 "engines": { 1097 - "node": ">=18" 1187 + "node": "^14.21.3 || >=16" 1188 + }, 1189 + "funding": { 1190 + "url": "https://paulmillr.com/funding/" 1098 1191 } 1099 1192 }, 1100 1193 "node_modules/@nodelib/fs.scandir": { ··· 1135 1228 "node": ">= 8" 1136 1229 } 1137 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 + }, 1138 1238 "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==", 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==", 1142 1242 "cpu": [ 1143 1243 "arm" 1144 1244 ], ··· 1150 1250 ] 1151 1251 }, 1152 1252 "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==", 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==", 1156 1256 "cpu": [ 1157 1257 "arm64" 1158 1258 ], ··· 1164 1264 ] 1165 1265 }, 1166 1266 "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==", 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==", 1170 1270 "cpu": [ 1171 1271 "arm64" 1172 1272 ], ··· 1178 1278 ] 1179 1279 }, 1180 1280 "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==", 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==", 1184 1284 "cpu": [ 1185 1285 "x64" 1186 1286 ], ··· 1192 1292 ] 1193 1293 }, 1194 1294 "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==", 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==", 1198 1298 "cpu": [ 1199 1299 "arm64" 1200 1300 ], ··· 1206 1306 ] 1207 1307 }, 1208 1308 "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==", 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==", 1212 1312 "cpu": [ 1213 1313 "x64" 1214 1314 ], ··· 1220 1320 ] 1221 1321 }, 1222 1322 "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==", 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==", 1226 1326 "cpu": [ 1227 1327 "arm" 1228 1328 ], ··· 1234 1334 ] 1235 1335 }, 1236 1336 "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==", 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==", 1240 1340 "cpu": [ 1241 1341 "arm" 1242 1342 ], ··· 1248 1348 ] 1249 1349 }, 1250 1350 "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==", 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==", 1254 1354 "cpu": [ 1255 1355 "arm64" 1256 1356 ], ··· 1262 1362 ] 1263 1363 }, 1264 1364 "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==", 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==", 1268 1368 "cpu": [ 1269 1369 "arm64" 1270 1370 ], ··· 1276 1376 ] 1277 1377 }, 1278 1378 "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==", 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==", 1282 1382 "cpu": [ 1283 1383 "loong64" 1284 1384 ], ··· 1290 1390 ] 1291 1391 }, 1292 1392 "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==", 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==", 1296 1396 "cpu": [ 1297 1397 "ppc64" 1298 1398 ], ··· 1304 1404 ] 1305 1405 }, 1306 1406 "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==", 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==", 1310 1410 "cpu": [ 1311 1411 "riscv64" 1312 1412 ], ··· 1318 1418 ] 1319 1419 }, 1320 1420 "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==", 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==", 1324 1424 "cpu": [ 1325 1425 "riscv64" 1326 1426 ], ··· 1332 1432 ] 1333 1433 }, 1334 1434 "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==", 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==", 1338 1438 "cpu": [ 1339 1439 "s390x" 1340 1440 ], ··· 1346 1446 ] 1347 1447 }, 1348 1448 "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==", 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==", 1352 1452 "cpu": [ 1353 1453 "x64" 1354 1454 ], ··· 1360 1460 ] 1361 1461 }, 1362 1462 "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==", 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==", 1366 1466 "cpu": [ 1367 1467 "x64" 1368 1468 ], ··· 1374 1474 ] 1375 1475 }, 1376 1476 "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==", 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==", 1380 1480 "cpu": [ 1381 1481 "arm64" 1382 1482 ], ··· 1388 1488 ] 1389 1489 }, 1390 1490 "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==", 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==", 1394 1494 "cpu": [ 1395 1495 "ia32" 1396 1496 ], ··· 1402 1502 ] 1403 1503 }, 1404 1504 "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==", 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==", 1408 1508 "cpu": [ 1409 1509 "x64" 1410 1510 ], ··· 1474 1574 "dev": true, 1475 1575 "license": "MIT" 1476 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 + }, 1477 1587 "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==", 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==", 1481 1591 "dev": true, 1482 1592 "license": "MIT", 1483 1593 "dependencies": { ··· 1485 1595 } 1486 1596 }, 1487 1597 "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==", 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==", 1491 1601 "dev": true, 1492 1602 "license": "MIT", 1493 1603 "peerDependencies": { ··· 1495 1605 } 1496 1606 }, 1497 1607 "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==", 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==", 1501 1611 "dev": true, 1502 1612 "license": "MIT", 1503 1613 "dependencies": { 1504 1614 "@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", 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", 1509 1619 "graphemer": "^1.4.0", 1510 - "ignore": "^5.3.1", 1620 + "ignore": "^7.0.0", 1511 1621 "natural-compare": "^1.4.0", 1512 - "ts-api-utils": "^2.0.1" 1622 + "ts-api-utils": "^2.1.0" 1513 1623 }, 1514 1624 "engines": { 1515 1625 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1519 1629 "url": "https://opencollective.com/typescript-eslint" 1520 1630 }, 1521 1631 "peerDependencies": { 1522 - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", 1632 + "@typescript-eslint/parser": "^8.33.0", 1523 1633 "eslint": "^8.57.0 || ^9.0.0", 1524 1634 "typescript": ">=4.8.4 <5.9.0" 1525 1635 } 1526 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 + }, 1527 1647 "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==", 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==", 1531 1651 "dev": true, 1532 1652 "license": "MIT", 1533 1653 "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", 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", 1538 1658 "debug": "^4.3.4" 1539 1659 }, 1540 1660 "engines": { ··· 1549 1669 "typescript": ">=4.8.4 <5.9.0" 1550 1670 } 1551 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 + }, 1552 1691 "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==", 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==", 1556 1695 "dev": true, 1557 1696 "license": "MIT", 1558 1697 "dependencies": { 1559 - "@typescript-eslint/types": "8.31.1", 1560 - "@typescript-eslint/visitor-keys": "8.31.1" 1698 + "@typescript-eslint/types": "8.33.0", 1699 + "@typescript-eslint/visitor-keys": "8.33.0" 1561 1700 }, 1562 1701 "engines": { 1563 1702 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1567 1706 "url": "https://opencollective.com/typescript-eslint" 1568 1707 } 1569 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 + }, 1570 1726 "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==", 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==", 1574 1730 "dev": true, 1575 1731 "license": "MIT", 1576 1732 "dependencies": { 1577 - "@typescript-eslint/typescript-estree": "8.31.1", 1578 - "@typescript-eslint/utils": "8.31.1", 1733 + "@typescript-eslint/typescript-estree": "8.33.0", 1734 + "@typescript-eslint/utils": "8.33.0", 1579 1735 "debug": "^4.3.4", 1580 - "ts-api-utils": "^2.0.1" 1736 + "ts-api-utils": "^2.1.0" 1581 1737 }, 1582 1738 "engines": { 1583 1739 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1592 1748 } 1593 1749 }, 1594 1750 "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==", 1751 + "version": "8.33.0", 1752 + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz", 1753 + "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==", 1598 1754 "dev": true, 1599 1755 "license": "MIT", 1600 1756 "engines": { ··· 1606 1762 } 1607 1763 }, 1608 1764 "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==", 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==", 1612 1768 "dev": true, 1613 1769 "license": "MIT", 1614 1770 "dependencies": { 1615 - "@typescript-eslint/types": "8.31.1", 1616 - "@typescript-eslint/visitor-keys": "8.31.1", 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", 1617 1775 "debug": "^4.3.4", 1618 1776 "fast-glob": "^3.3.2", 1619 1777 "is-glob": "^4.0.3", 1620 1778 "minimatch": "^9.0.4", 1621 1779 "semver": "^7.6.0", 1622 - "ts-api-utils": "^2.0.1" 1780 + "ts-api-utils": "^2.1.0" 1623 1781 }, 1624 1782 "engines": { 1625 1783 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1658 1816 "url": "https://github.com/sponsors/isaacs" 1659 1817 } 1660 1818 }, 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 1819 "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==", 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==", 1678 1823 "dev": true, 1679 1824 "license": "MIT", 1680 1825 "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" 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" 1685 1830 }, 1686 1831 "engines": { 1687 1832 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1696 1841 } 1697 1842 }, 1698 1843 "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==", 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==", 1702 1847 "dev": true, 1703 1848 "license": "MIT", 1704 1849 "dependencies": { 1705 - "@typescript-eslint/types": "8.31.1", 1850 + "@typescript-eslint/types": "8.33.0", 1706 1851 "eslint-visitor-keys": "^4.2.0" 1707 1852 }, 1708 1853 "engines": { ··· 1713 1858 "url": "https://opencollective.com/typescript-eslint" 1714 1859 } 1715 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 + }, 1716 1874 "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==", 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==", 1720 1878 "dev": true, 1721 1879 "license": "MIT", 1722 1880 "dependencies": { 1723 1881 "@babel/core": "^7.26.10", 1724 1882 "@babel/plugin-transform-react-jsx-self": "^7.25.9", 1725 1883 "@babel/plugin-transform-react-jsx-source": "^7.25.9", 1884 + "@rolldown/pluginutils": "1.0.0-beta.9", 1726 1885 "@types/babel__core": "^7.20.5", 1727 1886 "react-refresh": "^0.17.0" 1728 1887 }, ··· 1733 1892 "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" 1734 1893 } 1735 1894 }, 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 1895 "node_modules/acorn": { 1751 1896 "version": "8.14.1", 1752 1897 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", ··· 1787 1932 "url": "https://github.com/sponsors/epoberezkin" 1788 1933 } 1789 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 + }, 1790 1945 "node_modules/ansi-styles": { 1791 1946 "version": "4.3.0", 1792 1947 "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", ··· 1823 1978 "dev": true, 1824 1979 "license": "MIT" 1825 1980 }, 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 1981 "node_modules/brace-expansion": { 1848 1982 "version": "1.1.11", 1849 1983 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", ··· 1901 2035 "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 1902 2036 } 1903 2037 }, 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==", 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==", 1908 2042 "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 - } 2043 + "license": "MIT" 1944 2044 }, 1945 2045 "node_modules/callsites": { 1946 2046 "version": "3.1.0", ··· 1953 2053 } 1954 2054 }, 1955 2055 "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==", 2056 + "version": "1.0.30001718", 2057 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", 2058 + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", 1959 2059 "dev": true, 1960 2060 "funding": [ 1961 2061 { ··· 1990 2090 "url": "https://github.com/chalk/chalk?sponsor=1" 1991 2091 } 1992 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 + }, 1993 2108 "node_modules/color-convert": { 1994 2109 "version": "2.0.1", 1995 2110 "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", ··· 2010 2125 "dev": true, 2011 2126 "license": "MIT" 2012 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 + }, 2013 2135 "node_modules/concat-map": { 2014 2136 "version": "0.0.1", 2015 2137 "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", ··· 2017 2139 "dev": true, 2018 2140 "license": "MIT" 2019 2141 }, 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 2142 "node_modules/convert-source-map": { 2044 2143 "version": "2.0.0", 2045 2144 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", ··· 2048 2147 "license": "MIT" 2049 2148 }, 2050 2149 "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, 2150 + "version": "1.0.2", 2151 + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", 2152 + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", 2065 2153 "license": "MIT", 2066 2154 "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" 2155 + "node": ">=18" 2082 2156 } 2083 2157 }, 2084 2158 "node_modules/cross-spawn": { ··· 2104 2178 "license": "MIT" 2105 2179 }, 2106 2180 "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==", 2181 + "version": "4.4.1", 2182 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", 2183 + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", 2110 2184 "dev": true, 2111 2185 "license": "MIT", 2112 2186 "dependencies": { ··· 2128 2202 "dev": true, 2129 2203 "license": "MIT" 2130 2204 }, 2131 - "node_modules/depd": { 2205 + "node_modules/define-lazy-prop": { 2132 2206 "version": "2.0.0", 2133 - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 2134 - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 2207 + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", 2208 + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", 2135 2209 "dev": true, 2136 2210 "license": "MIT", 2137 2211 "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" 2212 + "node": ">=8" 2154 2213 } 2155 2214 }, 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 2215 "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==", 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==", 2167 2219 "dev": true, 2168 2220 "license": "ISC" 2169 2221 }, 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==", 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==", 2174 2226 "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 - } 2227 + "license": "MIT" 2212 2228 }, 2213 2229 "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==", 2230 + "version": "0.25.5", 2231 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", 2232 + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", 2217 2233 "dev": true, 2218 2234 "hasInstallScript": true, 2219 2235 "license": "MIT", ··· 2224 2240 "node": ">=18" 2225 2241 }, 2226 2242 "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" 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" 2252 2268 } 2253 2269 }, 2254 2270 "node_modules/escalade": { ··· 2261 2277 "node": ">=6" 2262 2278 } 2263 2279 }, 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 2280 "node_modules/escape-string-regexp": { 2272 2281 "version": "4.0.0", 2273 2282 "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", ··· 2282 2291 } 2283 2292 }, 2284 2293 "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==", 2294 + "version": "9.27.0", 2295 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", 2296 + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", 2288 2297 "dev": true, 2289 2298 "license": "MIT", 2290 2299 "dependencies": { ··· 2292 2301 "@eslint-community/regexpp": "^4.12.1", 2293 2302 "@eslint/config-array": "^0.20.0", 2294 2303 "@eslint/config-helpers": "^0.2.1", 2295 - "@eslint/core": "^0.13.0", 2304 + "@eslint/core": "^0.14.0", 2296 2305 "@eslint/eslintrc": "^3.3.1", 2297 - "@eslint/js": "9.26.0", 2298 - "@eslint/plugin-kit": "^0.2.8", 2306 + "@eslint/js": "9.27.0", 2307 + "@eslint/plugin-kit": "^0.3.1", 2299 2308 "@humanfs/node": "^0.16.6", 2300 2309 "@humanwhocodes/module-importer": "^1.0.1", 2301 2310 "@humanwhocodes/retry": "^0.4.2", 2302 - "@modelcontextprotocol/sdk": "^1.8.0", 2303 2311 "@types/estree": "^1.0.6", 2304 2312 "@types/json-schema": "^7.0.15", 2305 2313 "ajv": "^6.12.4", ··· 2323 2331 "lodash.merge": "^4.6.2", 2324 2332 "minimatch": "^3.1.2", 2325 2333 "natural-compare": "^1.4.0", 2326 - "optionator": "^0.9.3", 2327 - "zod": "^3.24.2" 2334 + "optionator": "^0.9.3" 2328 2335 }, 2329 2336 "bin": { 2330 2337 "eslint": "bin/eslint.js" ··· 2385 2392 } 2386 2393 }, 2387 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": { 2388 2408 "version": "4.2.0", 2389 2409 "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", 2390 2410 "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", ··· 2415 2435 "url": "https://opencollective.com/eslint" 2416 2436 } 2417 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 + }, 2418 2451 "node_modules/esquery": { 2419 2452 "version": "1.6.0", 2420 2453 "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", ··· 2461 2494 "node": ">=0.10.0" 2462 2495 } 2463 2496 }, 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 2497 "node_modules/fast-deep-equal": { 2557 2498 "version": "3.1.3", 2558 2499 "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", ··· 2640 2581 "node": ">=8" 2641 2582 } 2642 2583 }, 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 2584 "node_modules/find-up": { 2662 2585 "version": "5.0.0", 2663 2586 "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", ··· 2696 2619 "dev": true, 2697 2620 "license": "ISC" 2698 2621 }, 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 2622 "node_modules/fsevents": { 2720 2623 "version": "2.3.3", 2721 2624 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", ··· 2731 2634 "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 2732 2635 } 2733 2636 }, 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 2637 "node_modules/gensync": { 2745 2638 "version": "1.0.0-beta.2", 2746 2639 "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", ··· 2751 2644 "node": ">=6.9.0" 2752 2645 } 2753 2646 }, 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==", 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==", 2783 2651 "dev": true, 2784 - "license": "MIT", 2785 - "dependencies": { 2786 - "dunder-proto": "^1.0.1", 2787 - "es-object-atoms": "^1.0.0" 2788 - }, 2652 + "license": "ISC", 2789 2653 "engines": { 2790 - "node": ">= 0.4" 2654 + "node": "6.* || 8.* || >= 10.*" 2791 2655 } 2792 2656 }, 2793 2657 "node_modules/glob-parent": { ··· 2804 2668 } 2805 2669 }, 2806 2670 "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==", 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==", 2810 2674 "dev": true, 2811 2675 "license": "MIT", 2812 2676 "engines": { ··· 2816 2680 "url": "https://github.com/sponsors/sindresorhus" 2817 2681 } 2818 2682 }, 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 2683 "node_modules/graphemer": { 2833 2684 "version": "1.4.0", 2834 2685 "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", ··· 2845 2696 "node": ">=8" 2846 2697 } 2847 2698 }, 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 2699 "node_modules/ignore": { 2905 2700 "version": "5.3.2", 2906 2701 "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", ··· 2938 2733 "node": ">=0.8.19" 2939 2734 } 2940 2735 }, 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==", 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==", 2952 2740 "dev": true, 2953 2741 "license": "MIT", 2742 + "bin": { 2743 + "is-docker": "cli.js" 2744 + }, 2954 2745 "engines": { 2955 - "node": ">= 0.10" 2746 + "node": ">=8" 2747 + }, 2748 + "funding": { 2749 + "url": "https://github.com/sponsors/sindresorhus" 2956 2750 } 2957 2751 }, 2958 2752 "node_modules/is-extglob": { ··· 2965 2759 "node": ">=0.10.0" 2966 2760 } 2967 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 + }, 2968 2772 "node_modules/is-glob": { 2969 2773 "version": "4.0.3", 2970 2774 "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", ··· 2988 2792 "node": ">=0.12.0" 2989 2793 } 2990 2794 }, 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==", 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==", 2995 2799 "dev": true, 2996 - "license": "MIT" 2800 + "license": "MIT", 2801 + "dependencies": { 2802 + "is-docker": "^2.0.0" 2803 + }, 2804 + "engines": { 2805 + "node": ">=8" 2806 + } 2997 2807 }, 2998 2808 "node_modules/isexe": { 2999 2809 "version": "2.0.0", ··· 3007 2817 "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz", 3008 2818 "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==", 3009 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 + } 3010 2830 }, 3011 2831 "node_modules/js-tokens": { 3012 2832 "version": "4.0.0", ··· 3132 2952 "yallist": "^3.0.2" 3133 2953 } 3134 2954 }, 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 2955 "node_modules/merge2": { 3169 2956 "version": "1.4.1", 3170 2957 "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", ··· 3189 2976 "node": ">=8.6" 3190 2977 } 3191 2978 }, 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 2979 "node_modules/minimatch": { 3216 2980 "version": "3.1.2", 3217 2981 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", ··· 3233 2997 "license": "MIT" 3234 2998 }, 3235 2999 "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)" 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" 3240 3004 }, 3241 3005 "node_modules/nanoid": { 3242 3006 "version": "3.3.11", ··· 3264 3028 "dev": true, 3265 3029 "license": "MIT" 3266 3030 }, 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 3031 "node_modules/node-releases": { 3278 3032 "version": "2.0.19", 3279 3033 "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", ··· 3281 3035 "dev": true, 3282 3036 "license": "MIT" 3283 3037 }, 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==", 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==", 3298 3042 "dev": true, 3299 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 + }, 3300 3049 "engines": { 3301 - "node": ">= 0.4" 3050 + "node": ">=12" 3302 3051 }, 3303 3052 "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" 3053 + "url": "https://github.com/sponsors/sindresorhus" 3328 3054 } 3329 3055 }, 3330 3056 "node_modules/optionator": { ··· 3390 3116 "node": ">=6" 3391 3117 } 3392 3118 }, 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 3119 "node_modules/path-exists": { 3404 3120 "version": "4.0.0", 3405 3121 "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", ··· 3420 3136 "node": ">=8" 3421 3137 } 3422 3138 }, 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 3139 "node_modules/picocolors": { 3434 3140 "version": "1.1.1", 3435 3141 "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", ··· 3448 3154 }, 3449 3155 "funding": { 3450 3156 "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 3157 } 3462 3158 }, 3463 3159 "node_modules/postcss": { ··· 3499 3195 "node": ">= 0.8.0" 3500 3196 } 3501 3197 }, 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 3198 "node_modules/punycode": { 3517 3199 "version": "2.3.1", 3518 3200 "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", ··· 3523 3205 "node": ">=6" 3524 3206 } 3525 3207 }, 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 3208 "node_modules/queue-microtask": { 3543 3209 "version": "1.2.3", 3544 3210 "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", ··· 3560 3226 ], 3561 3227 "license": "MIT" 3562 3228 }, 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 3229 "node_modules/react": { 3590 3230 "version": "19.1.0", 3591 3231 "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", ··· 3618 3258 } 3619 3259 }, 3620 3260 "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==", 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==", 3624 3264 "license": "MIT", 3625 3265 "dependencies": { 3626 3266 "cookie": "^1.0.1", 3627 - "set-cookie-parser": "^2.6.0", 3628 - "turbo-stream": "2.4.0" 3267 + "set-cookie-parser": "^2.6.0" 3629 3268 }, 3630 3269 "engines": { 3631 3270 "node": ">=20.0.0" ··· 3641 3280 } 3642 3281 }, 3643 3282 "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==", 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==", 3647 3286 "license": "MIT", 3648 3287 "dependencies": { 3649 - "react-router": "7.5.3" 3288 + "react-router": "7.6.1" 3650 3289 }, 3651 3290 "engines": { 3652 3291 "node": ">=20.0.0" ··· 3656 3295 "react-dom": ">=18" 3657 3296 } 3658 3297 }, 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==", 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, 3663 3303 "license": "MIT", 3664 3304 "engines": { 3665 - "node": ">=18" 3305 + "node": ">=0.10.0" 3666 3306 } 3667 3307 }, 3668 3308 "node_modules/resolve-from": { ··· 3687 3327 } 3688 3328 }, 3689 3329 "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==", 3330 + "version": "4.41.1", 3331 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", 3332 + "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", 3693 3333 "dev": true, 3694 3334 "license": "MIT", 3695 3335 "dependencies": { ··· 3703 3343 "npm": ">=8.0.0" 3704 3344 }, 3705 3345 "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", 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", 3726 3366 "fsevents": "~2.3.2" 3727 3367 } 3728 3368 }, 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==", 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==", 3733 3373 "dev": true, 3734 3374 "license": "MIT", 3735 3375 "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" 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" 3741 3383 }, 3742 3384 "engines": { 3743 - "node": ">= 18" 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" 3744 3411 } 3745 3412 }, 3746 3413 "node_modules/run-parallel": { ··· 3767 3434 "queue-microtask": "^1.2.2" 3768 3435 } 3769 3436 }, 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 3437 "node_modules/scheduler": { 3799 3438 "version": "0.26.0", 3800 3439 "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", ··· 3802 3441 "license": "MIT" 3803 3442 }, 3804 3443 "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==", 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==", 3808 3447 "dev": true, 3809 3448 "license": "ISC", 3810 3449 "bin": { 3811 3450 "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 3451 }, 3833 3452 "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" 3453 + "node": ">=10" 3851 3454 } 3852 3455 }, 3853 3456 "node_modules/set-cookie-parser": { ··· 3855 3458 "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", 3856 3459 "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", 3857 3460 "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 3461 }, 3866 3462 "node_modules/shebang-command": { 3867 3463 "version": "2.0.0", ··· 3886 3482 "node": ">=8" 3887 3483 } 3888 3484 }, 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==", 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==", 3893 3489 "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 - }, 3490 + "license": "BSD-3-Clause", 3902 3491 "engines": { 3903 - "node": ">= 0.4" 3904 - }, 3905 - "funding": { 3906 - "url": "https://github.com/sponsors/ljharb" 3492 + "node": ">= 8" 3907 3493 } 3908 3494 }, 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==", 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==", 3913 3499 "dev": true, 3914 - "license": "MIT", 3915 - "dependencies": { 3916 - "es-errors": "^1.3.0", 3917 - "object-inspect": "^1.13.3" 3918 - }, 3500 + "license": "BSD-3-Clause", 3919 3501 "engines": { 3920 - "node": ">= 0.4" 3921 - }, 3922 - "funding": { 3923 - "url": "https://github.com/sponsors/ljharb" 3502 + "node": ">=0.10.0" 3924 3503 } 3925 3504 }, 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==", 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==", 3930 3509 "dev": true, 3931 3510 "license": "MIT", 3932 3511 "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 - }, 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", 3938 3522 "engines": { 3939 - "node": ">= 0.4" 3940 - }, 3941 - "funding": { 3942 - "url": "https://github.com/sponsors/ljharb" 3523 + "node": ">=0.10.0" 3943 3524 } 3944 3525 }, 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==", 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==", 3949 3530 "dev": true, 3950 3531 "license": "MIT", 3951 3532 "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" 3533 + "emoji-regex": "^8.0.0", 3534 + "is-fullwidth-code-point": "^3.0.0", 3535 + "strip-ansi": "^6.0.1" 3957 3536 }, 3958 3537 "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" 3538 + "node": ">=8" 3973 3539 } 3974 3540 }, 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==", 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==", 3979 3545 "dev": true, 3980 3546 "license": "MIT", 3547 + "dependencies": { 3548 + "ansi-regex": "^5.0.1" 3549 + }, 3981 3550 "engines": { 3982 - "node": ">= 0.8" 3551 + "node": ">=8" 3983 3552 } 3984 3553 }, 3985 3554 "node_modules/strip-json-comments": { ··· 4008 3577 "node": ">=8" 4009 3578 } 4010 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 + }, 4011 3599 "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==", 3600 + "version": "0.2.14", 3601 + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", 3602 + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", 4015 3603 "dev": true, 4016 3604 "license": "MIT", 4017 3605 "dependencies": { ··· 4026 3614 } 4027 3615 }, 4028 3616 "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==", 3617 + "version": "6.4.5", 3618 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", 3619 + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", 4032 3620 "dev": true, 4033 3621 "license": "MIT", 4034 3622 "peerDependencies": { ··· 4054 3642 } 4055 3643 }, 4056 3644 "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==", 3645 + "version": "1.259.0", 3646 + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.259.0.tgz", 3647 + "integrity": "sha512-AldGGlDP0PNgwppe2quAvuBl18UcjuNtOnDuUkqhd6ipPqrYYBt3aTxK1QTsBVknk97lS2JcafWMghjGWFtunw==", 4060 3648 "license": "MIT", 4061 3649 "bin": { 4062 3650 "tlds": "bin.js" ··· 4073 3661 }, 4074 3662 "engines": { 4075 3663 "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 3664 } 4087 3665 }, 4088 3666 "node_modules/ts-api-utils": { ··· 4098 3676 "typescript": ">=4.8.4" 4099 3677 } 4100 3678 }, 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 3679 "node_modules/type-check": { 4108 3680 "version": "0.4.0", 4109 3681 "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", ··· 4117 3689 "node": ">= 0.8.0" 4118 3690 } 4119 3691 }, 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 3692 "node_modules/typescript": { 4136 3693 "version": "5.7.3", 4137 3694 "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", ··· 4147 3704 } 4148 3705 }, 4149 3706 "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==", 3707 + "version": "8.33.0", 3708 + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.0.tgz", 3709 + "integrity": "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ==", 4153 3710 "dev": true, 4154 3711 "license": "MIT", 4155 3712 "dependencies": { 4156 - "@typescript-eslint/eslint-plugin": "8.31.1", 4157 - "@typescript-eslint/parser": "8.31.1", 4158 - "@typescript-eslint/utils": "8.31.1" 3713 + "@typescript-eslint/eslint-plugin": "8.33.0", 3714 + "@typescript-eslint/parser": "8.33.0", 3715 + "@typescript-eslint/utils": "8.33.0" 4159 3716 }, 4160 3717 "engines": { 4161 3718 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 4170 3727 } 4171 3728 }, 4172 3729 "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", 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", 4177 3734 "dependencies": { 4178 - "multiformats": "^9.4.2" 3735 + "multiformats": "^13.0.0" 4179 3736 } 4180 3737 }, 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==", 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==", 4185 3742 "dev": true, 4186 - "license": "MIT", 4187 - "engines": { 4188 - "node": ">= 0.8" 4189 - } 3743 + "license": "MIT" 4190 3744 }, 4191 3745 "node_modules/update-browserslist-db": { 4192 3746 "version": "1.1.3", ··· 4229 3783 "punycode": "^2.1.0" 4230 3784 } 4231 3785 }, 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 3786 "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==", 3787 + "version": "6.3.5", 3788 + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", 3789 + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", 4246 3790 "dev": true, 4247 3791 "license": "MIT", 4248 3792 "dependencies": { ··· 4315 3859 } 4316 3860 }, 4317 3861 "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==", 3862 + "version": "6.4.5", 3863 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", 3864 + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", 4321 3865 "dev": true, 4322 3866 "license": "MIT", 4323 3867 "peerDependencies": { ··· 4368 3912 "node": ">=0.10.0" 4369 3913 } 4370 3914 }, 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==", 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==", 4375 3937 "dev": true, 4376 - "license": "ISC" 3938 + "license": "ISC", 3939 + "engines": { 3940 + "node": ">=10" 3941 + } 4377 3942 }, 4378 3943 "node_modules/yallist": { 4379 3944 "version": "3.1.1", ··· 4382 3947 "dev": true, 4383 3948 "license": "ISC" 4384 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 + }, 4385 3979 "node_modules/yocto-queue": { 4386 3980 "version": "0.1.0", 4387 3981 "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", ··· 4396 3990 } 4397 3991 }, 4398 3992 "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==", 3993 + "version": "3.25.32", 3994 + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.32.tgz", 3995 + "integrity": "sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g==", 4402 3996 "license": "MIT", 4403 3997 "funding": { 4404 3998 "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 3999 } 4416 4000 } 4417 4001 }
+16 -5
package.json
··· 1 1 { 2 2 "name": "atproto-migration", 3 3 "private": true, 4 - "version": "0.0.0", 4 + "version": "0.1.0", 5 5 "type": "module", 6 6 "scripts": { 7 7 "dev": "vite", 8 8 "build": "tsc -b && vite build", 9 - "lint": "eslint .", 9 + "lint": "eslint src/", 10 10 "preview": "vite preview" 11 11 }, 12 12 "dependencies": { 13 13 "@atproto/api": "^0.15.5", 14 + "@atproto/crypto": "^0.4.4", 15 + "multiformats": "^13.3.6", 14 16 "react": "^19.0.0", 15 17 "react-dom": "^19.0.0", 16 - "react-router-dom": "^7.5.3" 18 + "react-router-dom": "^7.5.3", 19 + "uint8arrays": "^5.1.0" 17 20 }, 18 21 "devDependencies": { 22 + "@eslint/eslintrc": "^3.3.1", 19 23 "@eslint/js": "^9.22.0", 20 - "@types/react": "^19.0.10", 21 - "@types/react-dom": "^19.0.4", 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", 22 30 "@vitejs/plugin-react": "^4.3.4", 23 31 "eslint": "^9.22.0", 24 32 "eslint-plugin-react-hooks": "^5.2.0", 25 33 "eslint-plugin-react-refresh": "^0.4.19", 26 34 "globals": "^16.0.0", 35 + "jiti": "^2.4.2", 36 + "rollup-plugin-visualizer": "^6.0.1", 37 + "terser": "^5.40.0", 27 38 "typescript": "~5.7.2", 28 39 "typescript-eslint": "^8.26.1", 29 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 1 import { useNetwork } from '../../contexts/NetworkContext'; 2 - import '../../styles/App.css'; 3 2 4 3 export default function NetworkWarning() { 5 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' 1 + import { StrictMode, useState, useEffect } from 'react' 2 2 import { createRoot } from 'react-dom/client' 3 - import './styles/App.css' 4 - import App from './App.tsx' 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 + }; 5 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 6 75 createRoot(document.getElementById('root')!).render( 7 76 <StrictMode> 8 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 1 { 2 2 "compilerOptions": { 3 3 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 - "target": "ES2020", 4 + "target": "ES2023", 5 5 "useDefineForClassFields": true, 6 - "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 + "lib": ["ES2023", "DOM", "DOM.Iterable"], 7 7 "module": "ESNext", 8 8 "skipLibCheck": true, 9 9 10 10 /* Bundler mode */ 11 11 "moduleResolution": "bundler", 12 12 "allowImportingTsExtensions": true, 13 + "resolveJsonModule": true, 13 14 "isolatedModules": true, 14 15 "moduleDetection": "force", 15 16 "noEmit": true, ··· 20 21 "noUnusedLocals": true, 21 22 "noUnusedParameters": true, 22 23 "noFallthroughCasesInSwitch": true, 23 - "noUncheckedSideEffectImports": true 24 + "noUncheckedSideEffectImports": true, 25 + "forceConsistentCasingInFileNames": true, 26 + 27 + /* Types */ 28 + "types": ["react", "react-dom", "node"] 24 29 }, 25 - "include": ["src"] 30 + "include": ["src/**/*"], 31 + "references": [{ "path": "./tsconfig.node.json" }] 26 32 }
+5 -5
tsconfig.node.json
··· 1 1 { 2 2 "compilerOptions": { 3 3 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 - "target": "ES2022", 4 + "target": "ES2023", 5 5 "lib": ["ES2023"], 6 6 "module": "ESNext", 7 7 "skipLibCheck": true, 8 8 9 9 /* Bundler mode */ 10 - "moduleResolution": "bundler", 11 - "allowImportingTsExtensions": true, 10 + "moduleResolution": "node", 12 11 "isolatedModules": true, 13 12 "moduleDetection": "force", 14 - "noEmit": true, 13 + "composite": true, 15 14 16 15 /* Linting */ 17 16 "strict": true, 18 17 "noUnusedLocals": true, 19 18 "noUnusedParameters": true, 20 19 "noFallthroughCasesInSwitch": true, 21 - "noUncheckedSideEffectImports": true 20 + "noUncheckedSideEffectImports": true, 21 + "forceConsistentCasingInFileNames": true 22 22 }, 23 23 "include": ["vite.config.ts"] 24 24 }
+37 -3
vite.config.ts
··· 1 - import { defineConfig } from 'vite' 2 - import react from '@vitejs/plugin-react' 1 + import { defineConfig } from 'vite'; 2 + import react from '@vitejs/plugin-react'; 3 + import { visualizer } from 'rollup-plugin-visualizer'; 3 4 4 5 // https://vitejs.dev/config/ 5 6 export default defineConfig({ 6 - plugins: [react()], 7 + plugins: [ 8 + react(), 9 + ...(process.env.ANALYZE ? [visualizer({ 10 + open: true, 11 + filename: 'dist/bundle-analysis.html', 12 + gzipSize: true 13 + })] : []) 14 + ], 7 15 build: { 8 16 outDir: 'dist', 17 + emptyOutDir: true, 9 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 + } 10 43 }, 11 44 server: { 12 45 port: 3000, 13 46 }, 47 + publicDir: 'public' 14 48 })