handy online tools for AT Protocol boat.kelinci.net
atproto bluesky atcute typescript solidjs

Compare changes

Choose any two refs to compare.

+3
.gitignore
··· 1 1 node_modules/ 2 2 dist/ 3 3 4 + /.wrangler/ 5 + /.research/ 6 + 4 7 *.local 5 8 *.local.ts 6 9
-2
.npmrc
··· 1 1 auto-install-peers=false 2 2 public-hoist-pattern[]=workbox-window 3 - 4 - @jsr:registry=https://npm.jsr.io
+1 -1
.vscode/settings.json
··· 1 1 { 2 - "editor.defaultFormatter": "esbenp.prettier-vscode", 2 + "editor.defaultFormatter": "prettier.prettier-vscode", 3 3 "typescript.tsdk": "node_modules/typescript/lib", 4 4 "tailwindCSS.experimental.classRegex": ["tw`([^`]*)"] 5 5 }
+14
LICENSE
··· 1 + BSD Zero Clause License 2 + 3 + Copyright (c) 2025 Mary 4 + 5 + Permission to use, copy, modify, and/or distribute this software for any 6 + purpose with or without fee is hereby granted. 7 + 8 + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 + PERFORMANCE OF THIS SOFTWARE.
+32 -28
package.json
··· 4 4 "dev": "vite", 5 5 "build": "tsc -b && vite build", 6 6 "preview": "vite preview", 7 - "fmt": "prettier --cache --write ." 7 + "fmt": "PRETTIER_EXPERIMENTAL_CLI=1 prettier --cache --write ." 8 8 }, 9 9 "dependencies": { 10 - "@atcute/bluesky": "^1.0.14", 11 - "@atcute/car": "^3.0.0", 12 - "@atcute/cbor": "^2.2.0", 13 - "@atcute/cid": "^2.2.0", 14 - "@atcute/client": "^2.0.8", 15 - "@atcute/crypto": "^2.2.0", 16 - "@atcute/did-plc": "^0.1.1", 17 - "@atcute/identity": "^0.1.1", 18 - "@atcute/identity-resolver": "^0.1.2", 19 - "@atcute/multibase": "^1.1.2", 20 - "@atcute/tid": "^1.0.2", 21 - "@badrap/valita": "^0.4.3", 22 - "@mary/array-fns": "npm:@jsr/mary__array-fns@^0.1.4", 23 - "@mary/events": "npm:@jsr/mary__events@^0.1.0", 10 + "@atcute/atproto": "^3.1.9", 11 + "@atcute/bluesky": "^3.2.13", 12 + "@atcute/car": "^5.0.0", 13 + "@atcute/cbor": "^2.2.8", 14 + "@atcute/cid": "^2.2.6", 15 + "@atcute/client": "^4.1.1", 16 + "@atcute/crypto": "^2.3.0", 17 + "@atcute/did-plc": "^0.2.0", 18 + "@atcute/identity": "^1.1.3", 19 + "@atcute/identity-resolver": "^1.2.0", 20 + "@atcute/lexicons": "^1.2.5", 21 + "@atcute/multibase": "^1.1.6", 22 + "@atcute/repo": "^0.1.0", 23 + "@atcute/tid": "^1.0.3", 24 + "@badrap/valita": "^0.4.6", 25 + "@mary/array-fns": "jsr:^0.1.5", 26 + "@mary/ds-queue": "jsr:^0.1.3", 27 + "@mary/events": "jsr:^0.2.0", 24 28 "@mary/solid-freeze": "npm:@externdefs/solid-freeze@^0.1.1", 25 - "@mary/tar": "npm:@jsr/mary__tar@^0.2.4", 26 - "nanoid": "^5.1.5", 29 + "@mary/tar": "jsr:^0.3.1", 30 + "nanoid": "^5.1.6", 27 31 "native-file-system-adapter": "^3.0.1", 28 - "solid-js": "^1.9.5" 32 + "solid-js": "^1.9.10" 29 33 }, 30 34 "devDependencies": { 31 35 "@tailwindcss/forms": "^0.5.10", 32 - "@types/node": "^22.13.12", 33 - "autoprefixer": "^10.4.21", 34 - "prettier": "^3.5.3", 35 - "prettier-plugin-tailwindcss": "^0.6.11", 36 - "tailwindcss": "^3.4.17", 37 - "terser": "^5.39.0", 38 - "typescript": "5.8.2", 39 - "vite": "^6.2.2", 40 - "vite-plugin-solid": "^2.11.6", 41 - "wrangler": "^4.4.0" 36 + "@types/node": "^22.19.2", 37 + "autoprefixer": "^10.4.22", 38 + "prettier": "^3.7.4", 39 + "prettier-plugin-tailwindcss": "^0.6.14", 40 + "tailwindcss": "^3.4.18", 41 + "terser": "^5.44.1", 42 + "typescript": "~5.9.3", 43 + "vite": "^7.2.7", 44 + "vite-plugin-solid": "^2.11.10", 45 + "wrangler": "^4.53.0" 42 46 } 43 47 }
+1051 -1175
pnpm-lock.yaml
··· 8 8 9 9 .: 10 10 dependencies: 11 + '@atcute/atproto': 12 + specifier: ^3.1.9 13 + version: 3.1.9 11 14 '@atcute/bluesky': 12 - specifier: ^1.0.14 13 - version: 1.0.14(@atcute/client@2.0.8) 15 + specifier: ^3.2.13 16 + version: 3.2.13 14 17 '@atcute/car': 15 - specifier: ^3.0.0 16 - version: 3.0.0 18 + specifier: ^5.0.0 19 + version: 5.0.0 17 20 '@atcute/cbor': 18 - specifier: ^2.2.0 19 - version: 2.2.0 21 + specifier: ^2.2.8 22 + version: 2.2.8 20 23 '@atcute/cid': 21 - specifier: ^2.2.0 22 - version: 2.2.0 24 + specifier: ^2.2.6 25 + version: 2.2.6 23 26 '@atcute/client': 24 - specifier: ^2.0.8 25 - version: 2.0.8 27 + specifier: ^4.1.1 28 + version: 4.1.1 26 29 '@atcute/crypto': 27 - specifier: ^2.2.0 28 - version: 2.2.0 30 + specifier: ^2.3.0 31 + version: 2.3.0 29 32 '@atcute/did-plc': 30 - specifier: ^0.1.1 31 - version: 0.1.1 33 + specifier: ^0.2.0 34 + version: 0.2.0 32 35 '@atcute/identity': 33 - specifier: ^0.1.1 34 - version: 0.1.1 36 + specifier: ^1.1.3 37 + version: 1.1.3 35 38 '@atcute/identity-resolver': 36 - specifier: ^0.1.2 37 - version: 0.1.2(@atcute/identity@0.1.1) 39 + specifier: ^1.2.0 40 + version: 1.2.0(@atcute/identity@1.1.3) 41 + '@atcute/lexicons': 42 + specifier: ^1.2.5 43 + version: 1.2.5 38 44 '@atcute/multibase': 39 - specifier: ^1.1.2 40 - version: 1.1.2 45 + specifier: ^1.1.6 46 + version: 1.1.6 47 + '@atcute/repo': 48 + specifier: ^0.1.0 49 + version: 0.1.0 41 50 '@atcute/tid': 42 - specifier: ^1.0.2 43 - version: 1.0.2 51 + specifier: ^1.0.3 52 + version: 1.0.3 44 53 '@badrap/valita': 45 - specifier: ^0.4.3 46 - version: 0.4.3 54 + specifier: ^0.4.6 55 + version: 0.4.6 47 56 '@mary/array-fns': 48 - specifier: npm:@jsr/mary__array-fns@^0.1.4 49 - version: '@jsr/mary__array-fns@0.1.4' 57 + specifier: jsr:^0.1.5 58 + version: '@jsr/mary__array-fns@0.1.5' 59 + '@mary/ds-queue': 60 + specifier: jsr:^0.1.3 61 + version: '@jsr/mary__ds-queue@0.1.3' 50 62 '@mary/events': 51 - specifier: npm:@jsr/mary__events@^0.1.0 52 - version: '@jsr/mary__events@0.1.0' 63 + specifier: jsr:^0.2.0 64 + version: '@jsr/mary__events@0.2.0' 53 65 '@mary/solid-freeze': 54 66 specifier: npm:@externdefs/solid-freeze@^0.1.1 55 - version: '@externdefs/solid-freeze@0.1.1(solid-js@1.9.5)' 67 + version: '@externdefs/solid-freeze@0.1.1(solid-js@1.9.10)' 56 68 '@mary/tar': 57 - specifier: npm:@jsr/mary__tar@^0.2.4 58 - version: '@jsr/mary__tar@0.2.4' 69 + specifier: jsr:^0.3.1 70 + version: '@jsr/mary__tar@0.3.1' 59 71 nanoid: 60 - specifier: ^5.1.5 61 - version: 5.1.5 72 + specifier: ^5.1.6 73 + version: 5.1.6 62 74 native-file-system-adapter: 63 75 specifier: ^3.0.1 64 76 version: 3.0.1 65 77 solid-js: 66 - specifier: ^1.9.5 67 - version: 1.9.5 78 + specifier: ^1.9.10 79 + version: 1.9.10 68 80 devDependencies: 69 81 '@tailwindcss/forms': 70 82 specifier: ^0.5.10 71 - version: 0.5.10(tailwindcss@3.4.17) 83 + version: 0.5.10(tailwindcss@3.4.18) 72 84 '@types/node': 73 - specifier: ^22.13.12 74 - version: 22.13.12 85 + specifier: ^22.19.2 86 + version: 22.19.2 75 87 autoprefixer: 76 - specifier: ^10.4.21 77 - version: 10.4.21(postcss@8.5.3) 88 + specifier: ^10.4.22 89 + version: 10.4.22(postcss@8.5.6) 78 90 prettier: 79 - specifier: ^3.5.3 80 - version: 3.5.3 91 + specifier: ^3.7.4 92 + version: 3.7.4 81 93 prettier-plugin-tailwindcss: 82 - specifier: ^0.6.11 83 - version: 0.6.11(prettier@3.5.3) 94 + specifier: ^0.6.14 95 + version: 0.6.14(prettier@3.7.4) 84 96 tailwindcss: 85 - specifier: ^3.4.17 86 - version: 3.4.17 97 + specifier: ^3.4.18 98 + version: 3.4.18 87 99 terser: 88 - specifier: ^5.39.0 89 - version: 5.39.0 100 + specifier: ^5.44.1 101 + version: 5.44.1 90 102 typescript: 91 - specifier: 5.8.2 92 - version: 5.8.2 103 + specifier: ~5.9.3 104 + version: 5.9.3 93 105 vite: 94 - specifier: ^6.2.2 95 - version: 6.2.2(@types/node@22.13.12)(jiti@1.21.7)(terser@5.39.0)(yaml@2.7.0) 106 + specifier: ^7.2.7 107 + version: 7.2.7(@types/node@22.19.2)(jiti@1.21.7)(terser@5.44.1) 96 108 vite-plugin-solid: 97 - specifier: ^2.11.6 98 - version: 2.11.6(solid-js@1.9.5)(vite@6.2.2(@types/node@22.13.12)(jiti@1.21.7)(terser@5.39.0)(yaml@2.7.0)) 109 + specifier: ^2.11.10 110 + version: 2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@22.19.2)(jiti@1.21.7)(terser@5.44.1)) 99 111 wrangler: 100 - specifier: ^4.4.0 101 - version: 4.4.0 112 + specifier: ^4.53.0 113 + version: 4.53.0 102 114 103 115 packages: 104 116 ··· 106 118 resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} 107 119 engines: {node: '>=10'} 108 120 109 - '@ampproject/remapping@2.3.0': 110 - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 111 - engines: {node: '>=6.0.0'} 121 + '@atcute/atproto@3.1.9': 122 + resolution: {integrity: sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w==} 112 123 113 - '@atcute/bluesky@1.0.14': 114 - resolution: {integrity: sha512-1ZBfvYsC/Tq7bqKSDzBcQRPDFguFodewow8z+MNRjS6DO9tLUWiKjm1DmR6sJfLQ2ktxU57IZPjvsvVfBdwJ6w==} 115 - peerDependencies: 116 - '@atcute/client': ^1.0.0 || ^2.0.0 124 + '@atcute/bluesky@3.2.13': 125 + resolution: {integrity: sha512-ZG/mqsCjVU6zvH6XsRw+oQglrsdu5R7mnncMO+Ux0KWbX2xJw4ZMFHfs7ZTC69dVPK9r/yle7YbpygZTOWDM9A==} 117 126 118 - '@atcute/car@3.0.0': 119 - resolution: {integrity: sha512-DBcA1i+PwejbfdE2KNhxnMwl9fgEAQ6QIRz3sHKJkNXnKIzOX+pnQtADR03Yxif139fI5BoPmJilzNP09o3AfQ==} 127 + '@atcute/car@5.0.0': 128 + resolution: {integrity: sha512-OIY2xTXv8lSpZsDSn/UYQtJSMvDw5Hi4Q+uyvmiqSM+fht08QRAEq/nxa5YFciPZ3nfDFnZ3//EgJw7QhkSXLQ==} 120 129 121 - '@atcute/cbor@2.2.0': 122 - resolution: {integrity: sha512-W3ttcDJHiB5N6MbfEpJbTINZSjg0KB8gpBds8UWOGmybqUnuz43HZkFSpF/Vom0KilYg6QWU/ZM4X8SXZE+kow==} 130 + '@atcute/cbor@2.2.8': 131 + resolution: {integrity: sha512-UzOAN9BuN6JCXgn0ryV8qZuRJUDrNqrbLd6EFM8jc6RYssjRyGRxNy6RZ1NU/07Hd8Tq/0pz8+nQiMu5Zai5uw==} 123 132 124 - '@atcute/cid@2.2.0': 125 - resolution: {integrity: sha512-ty+WjGcTfi1JJtcRNz6bsJMqT69lEIl4Ts7vl4U8SBHbxjbb6Tk/pcnQZVClKRYduRoZ1XhS9n5qOSIbIMctDA==} 133 + '@atcute/cid@2.2.6': 134 + resolution: {integrity: sha512-bTAHHbJ24p+E//V4KCS4xdmd39o211jJswvqQOevj7vk+5IYcgDLx1ryZWZ1sEPOo9x875li/kj5gpKL14RDwQ==} 126 135 127 - '@atcute/client@2.0.8': 128 - resolution: {integrity: sha512-OTfiWwjB4mOTlp2InGStvoQ+PIA5lvih9cTYU8BvOhzNcCBUpt4l860MKZExHjvQ9Tt1kjq/ED9zRiUjsAgIxw==} 136 + '@atcute/client@4.1.1': 137 + resolution: {integrity: sha512-FROCbTTCeL5u4tO/n72jDEKyKqjdlXMB56Ehve3W/gnnLGCYWvN42sS7tvL1Mgu6sbO3yZwsXKDrmM2No4XpjA==} 129 138 130 - '@atcute/crypto@2.2.0': 131 - resolution: {integrity: sha512-Q/64Qn1AI8J0ZNy4hPDPpW/3poKf4OWRUxIYceCDI+btEOcIG5YMlhEQeZd6ojnI3oBMXy03sbOktekaBYRK9Q==} 139 + '@atcute/crypto@2.3.0': 140 + resolution: {integrity: sha512-w5pkJKCjbNMQu+F4JRHbR3ROQyhi1wbn+GSC6WDQamcYHkZmEZk1/eoI354bIQOOfkEM6aFLv718iskrkon4GQ==} 132 141 133 - '@atcute/did-plc@0.1.1': 134 - resolution: {integrity: sha512-Yk18GUxWaEvHfH1CfPB4n2OOWVMcMIn1jKkeLzSykhLSbLcobCcA17LDY13pRG1b8PFdPIGUALKdquCTEk1CmA==} 142 + '@atcute/did-plc@0.2.0': 143 + resolution: {integrity: sha512-1sGek8GRM/Ph7nLVRREm8FqM7g4shGckItvdVwJcRbUa8Rh0zOsXQa0QyYWAC0k40BhkqO9FwKXhJEaXCmF5oQ==} 135 144 136 - '@atcute/identity-resolver@0.1.2': 137 - resolution: {integrity: sha512-fP2VbHD04kVcCdNi/Kszo6jFzqM7Pg3p33oGhfp2zVkwFKaVBlwCaFRWEga/Xvu/IDLwNdASGWnLqoA34SFeSg==} 145 + '@atcute/identity-resolver@1.2.0': 146 + resolution: {integrity: sha512-5UbSJfdV3JIkF8ksXz7g4nKBWasf2wROvzM66cfvTIWydWFO6/oS1KZd+zo9Eokje5Scf5+jsY9ZfgVARLepXg==} 138 147 peerDependencies: 139 - '@atcute/identity': ^0.1.0 148 + '@atcute/identity': ^1.0.0 140 149 141 - '@atcute/identity@0.1.1': 142 - resolution: {integrity: sha512-TijKOgvvOfp/QoMAqaiKLn+FnQi5XrxsWLVcVnvr5JoKlgF2yppNvVo0y62XEXZbgDuEMSav1v1tEjC4Hn7MzQ==} 150 + '@atcute/identity@1.1.3': 151 + resolution: {integrity: sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng==} 143 152 144 - '@atcute/multibase@1.1.2': 145 - resolution: {integrity: sha512-KFX+c7a/u2jSNcRw0rLaUHG/XEKf1A1c8XF5soHnsb1JMCShihf/anfZ1kJ4no/IlIp9HEHV3PQRQO2sWL6ASQ==} 153 + '@atcute/lexicons@1.2.5': 154 + resolution: {integrity: sha512-9yO9WdgxW8jZ7SbzUycH710z+JmsQ9W9n5S6i6eghYju32kkluFmgBeS47r8e8p2+Dv4DemS7o/3SUGsX9FR5Q==} 146 155 147 - '@atcute/tid@1.0.2': 148 - resolution: {integrity: sha512-ahmjroNyeDPJhtuf3+HTJropaH04HmJ8fhntDu73Gpz/RkAF7+nkz6kcP2QTgfvMCgMPAJUdskAAP82GPDTY9w==} 156 + '@atcute/mst@0.1.0': 157 + resolution: {integrity: sha512-h+iDToKEnBpigk2DOHjSqY63vJtjYKUIztqu1CZ0P+I54wV2SrgoqAXAT1xrW6A1Iup8cjTv+U2H5WVG4KxPLw==} 158 + 159 + '@atcute/multibase@1.1.6': 160 + resolution: {integrity: sha512-HBxuCgYLKPPxETV0Rot4VP9e24vKl8JdzGCZOVsDaOXJgbRZoRIF67Lp0H/OgnJeH/Xpva8Z5ReoTNJE5dn3kg==} 149 161 150 - '@atcute/uint8array@1.0.1': 151 - resolution: {integrity: sha512-AAnlFKyfDRgb9GNZJbhQ6OuMhbmNPirQyapb8KnmcEhxQZ3+tt+4NcwqekEegY4MpNqSTYeeTdyxq0wGZv1JHg==} 162 + '@atcute/repo@0.1.0': 163 + resolution: {integrity: sha512-INiYAuma8dydBu7cqd2WVpcXh3mzhIepYBUqFWAK5MqMulPRLTRCc/9GW3G9pxYrOdlvLCVamG2Jf8XK0nuFEw==} 164 + 165 + '@atcute/tid@1.0.3': 166 + resolution: {integrity: sha512-wfMJx1IMdnu0CZgWl0uR4JO2s6PGT1YPhpytD4ZHzEYKKQVuqV6Eb/7vieaVo1eYNMp2FrY67FZObeR7utRl2w==} 167 + 168 + '@atcute/uint8array@1.0.6': 169 + resolution: {integrity: sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A==} 170 + 171 + '@atcute/util-fetch@1.0.4': 172 + resolution: {integrity: sha512-sIU9Qk0dE8PLEXSfhy+gIJV+HpiiknMytCI2SqLlqd0vgZUtEKI/EQfP+23LHWvP+CLCzVDOa6cpH045OlmNBg==} 152 173 153 - '@atcute/util-fetch@1.0.1': 154 - resolution: {integrity: sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow==} 174 + '@atcute/varint@1.0.3': 175 + resolution: {integrity: sha512-fdvMPyBB+McDT+Ai5e9RwEbwYV4yjZ60S2Dn5PTjGqUyxvoCH1z42viuheDZRUDkmfQehXJTZ5az7dSozVNtog==} 155 176 156 - '@atcute/varint@1.0.2': 157 - resolution: {integrity: sha512-0O31hePzzr4O3NGWHUKKOyta6CGSL+AtN8iir8grGxu9jXyI7DBARlw6PbgKA6uTAvsXdpmRmF8MX+p0TsLnNg==} 177 + '@babel/code-frame@7.27.1': 178 + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} 179 + engines: {node: '>=6.9.0'} 158 180 159 - '@babel/code-frame@7.26.2': 160 - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} 181 + '@babel/compat-data@7.28.5': 182 + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} 161 183 engines: {node: '>=6.9.0'} 162 184 163 - '@babel/compat-data@7.26.8': 164 - resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} 185 + '@babel/core@7.28.5': 186 + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} 165 187 engines: {node: '>=6.9.0'} 166 188 167 - '@babel/core@7.26.10': 168 - resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} 189 + '@babel/generator@7.28.5': 190 + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} 169 191 engines: {node: '>=6.9.0'} 170 192 171 - '@babel/generator@7.26.10': 172 - resolution: {integrity: sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==} 193 + '@babel/helper-compilation-targets@7.27.2': 194 + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} 173 195 engines: {node: '>=6.9.0'} 174 196 175 - '@babel/helper-compilation-targets@7.26.5': 176 - resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} 197 + '@babel/helper-globals@7.28.0': 198 + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} 177 199 engines: {node: '>=6.9.0'} 178 200 179 201 '@babel/helper-module-imports@7.18.6': 180 202 resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} 181 203 engines: {node: '>=6.9.0'} 182 204 183 - '@babel/helper-module-imports@7.25.9': 184 - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} 205 + '@babel/helper-module-imports@7.27.1': 206 + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} 185 207 engines: {node: '>=6.9.0'} 186 208 187 - '@babel/helper-module-transforms@7.26.0': 188 - resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} 209 + '@babel/helper-module-transforms@7.28.3': 210 + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} 189 211 engines: {node: '>=6.9.0'} 190 212 peerDependencies: 191 213 '@babel/core': ^7.0.0 192 214 193 - '@babel/helper-plugin-utils@7.26.5': 194 - resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} 215 + '@babel/helper-plugin-utils@7.27.1': 216 + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} 195 217 engines: {node: '>=6.9.0'} 196 218 197 - '@babel/helper-string-parser@7.25.9': 198 - resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} 219 + '@babel/helper-string-parser@7.27.1': 220 + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} 199 221 engines: {node: '>=6.9.0'} 200 222 201 - '@babel/helper-validator-identifier@7.25.9': 202 - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} 223 + '@babel/helper-validator-identifier@7.28.5': 224 + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} 203 225 engines: {node: '>=6.9.0'} 204 226 205 - '@babel/helper-validator-option@7.25.9': 206 - resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} 227 + '@babel/helper-validator-option@7.27.1': 228 + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} 207 229 engines: {node: '>=6.9.0'} 208 230 209 - '@babel/helpers@7.26.10': 210 - resolution: {integrity: sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==} 231 + '@babel/helpers@7.28.4': 232 + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} 211 233 engines: {node: '>=6.9.0'} 212 234 213 - '@babel/parser@7.26.10': 214 - resolution: {integrity: sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==} 235 + '@babel/parser@7.28.5': 236 + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} 215 237 engines: {node: '>=6.0.0'} 216 238 hasBin: true 217 239 218 - '@babel/plugin-syntax-jsx@7.25.9': 219 - resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} 240 + '@babel/plugin-syntax-jsx@7.27.1': 241 + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} 220 242 engines: {node: '>=6.9.0'} 221 243 peerDependencies: 222 244 '@babel/core': ^7.0.0-0 223 245 224 - '@babel/template@7.26.9': 225 - resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} 246 + '@babel/template@7.27.2': 247 + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} 226 248 engines: {node: '>=6.9.0'} 227 249 228 - '@babel/traverse@7.26.10': 229 - resolution: {integrity: sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==} 250 + '@babel/traverse@7.28.5': 251 + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} 230 252 engines: {node: '>=6.9.0'} 231 253 232 - '@babel/types@7.26.10': 233 - resolution: {integrity: sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==} 254 + '@babel/types@7.28.5': 255 + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} 234 256 engines: {node: '>=6.9.0'} 235 257 236 - '@badrap/valita@0.4.3': 237 - resolution: {integrity: sha512-C9iZSrVlTb610dxZ2oatK5LwefaHv0Q9eYfVDH3co846x7WinhCfc8jCDTE55yM8WxlmOfX2ckKmsSr7KzZ/gg==} 258 + '@badrap/valita@0.4.6': 259 + resolution: {integrity: sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==} 238 260 engines: {node: '>= 18'} 239 261 240 - '@cloudflare/kv-asset-handler@0.4.0': 241 - resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==} 262 + '@cloudflare/kv-asset-handler@0.4.1': 263 + resolution: {integrity: sha512-Nu8ahitGFFJztxUml9oD/DLb7Z28C8cd8F46IVQ7y5Btz575pvMY8AqZsXkX7Gds29eCKdMgIHjIvzskHgPSFg==} 242 264 engines: {node: '>=18.0.0'} 243 265 244 - '@cloudflare/unenv-preset@2.3.0': 245 - resolution: {integrity: sha512-AaKYnbFpHaVDZGh3Hjy3oLYd12+LZw9aupAOudYJ+tjekahxcIqlSAr0zK9kPOdtgn10tzaqH7QJFUWcLE+k7g==} 266 + '@cloudflare/unenv-preset@2.7.13': 267 + resolution: {integrity: sha512-NulO1H8R/DzsJguLC0ndMuk4Ufv0KSlN+E54ay9rn9ZCQo0kpAPwwh3LhgpZ96a3Dr6L9LqW57M4CqC34iLOvw==} 246 268 peerDependencies: 247 - unenv: 2.0.0-rc.15 248 - workerd: ^1.20250311.0 269 + unenv: 2.0.0-rc.24 270 + workerd: ^1.20251202.0 249 271 peerDependenciesMeta: 250 272 workerd: 251 273 optional: true 252 274 253 - '@cloudflare/workerd-darwin-64@1.20250320.0': 254 - resolution: {integrity: sha512-wS2fcowxgbrKtfahU0Mtt/0XYjnuAjZd+2FsTZ3GDgxlywVTTl8SeApM11cjYo7QNdGh56HEGYMsYojya5sHHQ==} 275 + '@cloudflare/workerd-darwin-64@1.20251202.0': 276 + resolution: {integrity: sha512-/uvEAWEukTWb1geHhbjGUeZqcSSSyYzp0mvoPUBl+l0ont4NVGao3fgwM0q8wtKvgoKCHSG6zcG23wj9Opj3Nw==} 255 277 engines: {node: '>=16'} 256 278 cpu: [x64] 257 279 os: [darwin] 258 280 259 - '@cloudflare/workerd-darwin-arm64@1.20250320.0': 260 - resolution: {integrity: sha512-QMqFay2buv3pPE+mi30QenX/cmlaB72sXTspk5e4LwEEgsxpoS8BryeIOeo8ScGDyt0NBfOutCRFTTiZLSqyzQ==} 281 + '@cloudflare/workerd-darwin-arm64@1.20251202.0': 282 + resolution: {integrity: sha512-f52xRvcI9cWRd6400EZStRtXiRC5XKEud7K5aFIbbUv0VeINltujFQQ9nHWtsF6g1quIXWkjhh5u01gPAYNNXA==} 261 283 engines: {node: '>=16'} 262 284 cpu: [arm64] 263 285 os: [darwin] 264 286 265 - '@cloudflare/workerd-linux-64@1.20250320.0': 266 - resolution: {integrity: sha512-PBkmZdNtSIBRiFUhEMhkDoR5WX0bZWE+nSys0/v6DeFU3Pc6KiH+2VPGqWOLVH85uzL1wWFpAJk9ptsWwTC9Ww==} 287 + '@cloudflare/workerd-linux-64@1.20251202.0': 288 + resolution: {integrity: sha512-HYXinF5RBH7oXbsFUMmwKCj+WltpYbf5mRKUBG5v3EuPhUjSIFB84U+58pDyfBJjcynHdy3EtvTWcvh/+lcgow==} 267 289 engines: {node: '>=16'} 268 290 cpu: [x64] 269 291 os: [linux] 270 292 271 - '@cloudflare/workerd-linux-arm64@1.20250320.0': 272 - resolution: {integrity: sha512-nHSMsNbUwaOJRYuHYK4EcZreOP3FlFqD47FUxGP6k1tjYs4l4z86XJMONbY8vE9WZ9BWPAzZX/xzSalB0DhGIA==} 293 + '@cloudflare/workerd-linux-arm64@1.20251202.0': 294 + resolution: {integrity: sha512-++L02Jdoxz7hEA9qDaQjbVU1RzQS+S+eqIi22DkPe2Tgiq2M3UfNpeu+75k5L9DGRIkZPYvwMBMbcmKvQqdIIg==} 273 295 engines: {node: '>=16'} 274 296 cpu: [arm64] 275 297 os: [linux] 276 298 277 - '@cloudflare/workerd-windows-64@1.20250320.0': 278 - resolution: {integrity: sha512-Uj5z/PyGqO8xuVCkS19exmQ5yGcC1RbB3nUaf6j5rlft7lBTBkjC+l7NAhEiRxNKaZuT2Lfy+r4vAEPsiotegw==} 299 + '@cloudflare/workerd-windows-64@1.20251202.0': 300 + resolution: {integrity: sha512-gzeU6eDydTi7ib+Q9DD/c0hpXtqPucnHk2tfGU03mljPObYxzMkkPGgB5qxpksFvub3y4K0ChjqYxGJB4F+j3g==} 279 301 engines: {node: '>=16'} 280 302 cpu: [x64] 281 303 os: [win32] ··· 284 306 resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} 285 307 engines: {node: '>=12'} 286 308 287 - '@emnapi/runtime@1.3.1': 288 - resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} 309 + '@emnapi/runtime@1.7.1': 310 + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} 289 311 290 - '@esbuild/aix-ppc64@0.24.2': 291 - resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} 312 + '@esbuild/aix-ppc64@0.25.12': 313 + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} 292 314 engines: {node: '>=18'} 293 315 cpu: [ppc64] 294 316 os: [aix] 295 317 296 - '@esbuild/aix-ppc64@0.25.1': 297 - resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} 318 + '@esbuild/aix-ppc64@0.27.0': 319 + resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} 298 320 engines: {node: '>=18'} 299 321 cpu: [ppc64] 300 322 os: [aix] 301 323 302 - '@esbuild/android-arm64@0.24.2': 303 - resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} 324 + '@esbuild/android-arm64@0.25.12': 325 + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} 304 326 engines: {node: '>=18'} 305 327 cpu: [arm64] 306 328 os: [android] 307 329 308 - '@esbuild/android-arm64@0.25.1': 309 - resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} 330 + '@esbuild/android-arm64@0.27.0': 331 + resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} 310 332 engines: {node: '>=18'} 311 333 cpu: [arm64] 312 334 os: [android] 313 335 314 - '@esbuild/android-arm@0.24.2': 315 - resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} 336 + '@esbuild/android-arm@0.25.12': 337 + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} 316 338 engines: {node: '>=18'} 317 339 cpu: [arm] 318 340 os: [android] 319 341 320 - '@esbuild/android-arm@0.25.1': 321 - resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} 342 + '@esbuild/android-arm@0.27.0': 343 + resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} 322 344 engines: {node: '>=18'} 323 345 cpu: [arm] 324 346 os: [android] 325 347 326 - '@esbuild/android-x64@0.24.2': 327 - resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} 348 + '@esbuild/android-x64@0.25.12': 349 + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} 328 350 engines: {node: '>=18'} 329 351 cpu: [x64] 330 352 os: [android] 331 353 332 - '@esbuild/android-x64@0.25.1': 333 - resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} 354 + '@esbuild/android-x64@0.27.0': 355 + resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} 334 356 engines: {node: '>=18'} 335 357 cpu: [x64] 336 358 os: [android] 337 359 338 - '@esbuild/darwin-arm64@0.24.2': 339 - resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} 360 + '@esbuild/darwin-arm64@0.25.12': 361 + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} 340 362 engines: {node: '>=18'} 341 363 cpu: [arm64] 342 364 os: [darwin] 343 365 344 - '@esbuild/darwin-arm64@0.25.1': 345 - resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} 366 + '@esbuild/darwin-arm64@0.27.0': 367 + resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} 346 368 engines: {node: '>=18'} 347 369 cpu: [arm64] 348 370 os: [darwin] 349 371 350 - '@esbuild/darwin-x64@0.24.2': 351 - resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} 372 + '@esbuild/darwin-x64@0.25.12': 373 + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} 352 374 engines: {node: '>=18'} 353 375 cpu: [x64] 354 376 os: [darwin] 355 377 356 - '@esbuild/darwin-x64@0.25.1': 357 - resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} 378 + '@esbuild/darwin-x64@0.27.0': 379 + resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} 358 380 engines: {node: '>=18'} 359 381 cpu: [x64] 360 382 os: [darwin] 361 383 362 - '@esbuild/freebsd-arm64@0.24.2': 363 - resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} 384 + '@esbuild/freebsd-arm64@0.25.12': 385 + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} 364 386 engines: {node: '>=18'} 365 387 cpu: [arm64] 366 388 os: [freebsd] 367 389 368 - '@esbuild/freebsd-arm64@0.25.1': 369 - resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} 390 + '@esbuild/freebsd-arm64@0.27.0': 391 + resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} 370 392 engines: {node: '>=18'} 371 393 cpu: [arm64] 372 394 os: [freebsd] 373 395 374 - '@esbuild/freebsd-x64@0.24.2': 375 - resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} 396 + '@esbuild/freebsd-x64@0.25.12': 397 + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} 376 398 engines: {node: '>=18'} 377 399 cpu: [x64] 378 400 os: [freebsd] 379 401 380 - '@esbuild/freebsd-x64@0.25.1': 381 - resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} 402 + '@esbuild/freebsd-x64@0.27.0': 403 + resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} 382 404 engines: {node: '>=18'} 383 405 cpu: [x64] 384 406 os: [freebsd] 385 407 386 - '@esbuild/linux-arm64@0.24.2': 387 - resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} 408 + '@esbuild/linux-arm64@0.25.12': 409 + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} 388 410 engines: {node: '>=18'} 389 411 cpu: [arm64] 390 412 os: [linux] 391 413 392 - '@esbuild/linux-arm64@0.25.1': 393 - resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} 414 + '@esbuild/linux-arm64@0.27.0': 415 + resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} 394 416 engines: {node: '>=18'} 395 417 cpu: [arm64] 396 418 os: [linux] 397 419 398 - '@esbuild/linux-arm@0.24.2': 399 - resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} 420 + '@esbuild/linux-arm@0.25.12': 421 + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} 400 422 engines: {node: '>=18'} 401 423 cpu: [arm] 402 424 os: [linux] 403 425 404 - '@esbuild/linux-arm@0.25.1': 405 - resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} 426 + '@esbuild/linux-arm@0.27.0': 427 + resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} 406 428 engines: {node: '>=18'} 407 429 cpu: [arm] 408 430 os: [linux] 409 431 410 - '@esbuild/linux-ia32@0.24.2': 411 - resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} 432 + '@esbuild/linux-ia32@0.25.12': 433 + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} 412 434 engines: {node: '>=18'} 413 435 cpu: [ia32] 414 436 os: [linux] 415 437 416 - '@esbuild/linux-ia32@0.25.1': 417 - resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} 438 + '@esbuild/linux-ia32@0.27.0': 439 + resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} 418 440 engines: {node: '>=18'} 419 441 cpu: [ia32] 420 442 os: [linux] 421 443 422 - '@esbuild/linux-loong64@0.24.2': 423 - resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} 444 + '@esbuild/linux-loong64@0.25.12': 445 + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} 424 446 engines: {node: '>=18'} 425 447 cpu: [loong64] 426 448 os: [linux] 427 449 428 - '@esbuild/linux-loong64@0.25.1': 429 - resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} 450 + '@esbuild/linux-loong64@0.27.0': 451 + resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} 430 452 engines: {node: '>=18'} 431 453 cpu: [loong64] 432 454 os: [linux] 433 455 434 - '@esbuild/linux-mips64el@0.24.2': 435 - resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} 456 + '@esbuild/linux-mips64el@0.25.12': 457 + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} 436 458 engines: {node: '>=18'} 437 459 cpu: [mips64el] 438 460 os: [linux] 439 461 440 - '@esbuild/linux-mips64el@0.25.1': 441 - resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} 462 + '@esbuild/linux-mips64el@0.27.0': 463 + resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} 442 464 engines: {node: '>=18'} 443 465 cpu: [mips64el] 444 466 os: [linux] 445 467 446 - '@esbuild/linux-ppc64@0.24.2': 447 - resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} 468 + '@esbuild/linux-ppc64@0.25.12': 469 + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} 448 470 engines: {node: '>=18'} 449 471 cpu: [ppc64] 450 472 os: [linux] 451 473 452 - '@esbuild/linux-ppc64@0.25.1': 453 - resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} 474 + '@esbuild/linux-ppc64@0.27.0': 475 + resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} 454 476 engines: {node: '>=18'} 455 477 cpu: [ppc64] 456 478 os: [linux] 457 479 458 - '@esbuild/linux-riscv64@0.24.2': 459 - resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} 480 + '@esbuild/linux-riscv64@0.25.12': 481 + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} 460 482 engines: {node: '>=18'} 461 483 cpu: [riscv64] 462 484 os: [linux] 463 485 464 - '@esbuild/linux-riscv64@0.25.1': 465 - resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} 486 + '@esbuild/linux-riscv64@0.27.0': 487 + resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} 466 488 engines: {node: '>=18'} 467 489 cpu: [riscv64] 468 490 os: [linux] 469 491 470 - '@esbuild/linux-s390x@0.24.2': 471 - resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} 492 + '@esbuild/linux-s390x@0.25.12': 493 + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} 472 494 engines: {node: '>=18'} 473 495 cpu: [s390x] 474 496 os: [linux] 475 497 476 - '@esbuild/linux-s390x@0.25.1': 477 - resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} 498 + '@esbuild/linux-s390x@0.27.0': 499 + resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} 478 500 engines: {node: '>=18'} 479 501 cpu: [s390x] 480 502 os: [linux] 481 503 482 - '@esbuild/linux-x64@0.24.2': 483 - resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} 504 + '@esbuild/linux-x64@0.25.12': 505 + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} 484 506 engines: {node: '>=18'} 485 507 cpu: [x64] 486 508 os: [linux] 487 509 488 - '@esbuild/linux-x64@0.25.1': 489 - resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} 510 + '@esbuild/linux-x64@0.27.0': 511 + resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} 490 512 engines: {node: '>=18'} 491 513 cpu: [x64] 492 514 os: [linux] 493 515 494 - '@esbuild/netbsd-arm64@0.24.2': 495 - resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} 516 + '@esbuild/netbsd-arm64@0.25.12': 517 + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} 496 518 engines: {node: '>=18'} 497 519 cpu: [arm64] 498 520 os: [netbsd] 499 521 500 - '@esbuild/netbsd-arm64@0.25.1': 501 - resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} 522 + '@esbuild/netbsd-arm64@0.27.0': 523 + resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} 502 524 engines: {node: '>=18'} 503 525 cpu: [arm64] 504 526 os: [netbsd] 505 527 506 - '@esbuild/netbsd-x64@0.24.2': 507 - resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} 528 + '@esbuild/netbsd-x64@0.25.12': 529 + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} 508 530 engines: {node: '>=18'} 509 531 cpu: [x64] 510 532 os: [netbsd] 511 533 512 - '@esbuild/netbsd-x64@0.25.1': 513 - resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} 534 + '@esbuild/netbsd-x64@0.27.0': 535 + resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} 514 536 engines: {node: '>=18'} 515 537 cpu: [x64] 516 538 os: [netbsd] 517 539 518 - '@esbuild/openbsd-arm64@0.24.2': 519 - resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} 540 + '@esbuild/openbsd-arm64@0.25.12': 541 + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} 520 542 engines: {node: '>=18'} 521 543 cpu: [arm64] 522 544 os: [openbsd] 523 545 524 - '@esbuild/openbsd-arm64@0.25.1': 525 - resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} 546 + '@esbuild/openbsd-arm64@0.27.0': 547 + resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} 526 548 engines: {node: '>=18'} 527 549 cpu: [arm64] 528 550 os: [openbsd] 529 551 530 - '@esbuild/openbsd-x64@0.24.2': 531 - resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} 552 + '@esbuild/openbsd-x64@0.25.12': 553 + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} 532 554 engines: {node: '>=18'} 533 555 cpu: [x64] 534 556 os: [openbsd] 535 557 536 - '@esbuild/openbsd-x64@0.25.1': 537 - resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} 558 + '@esbuild/openbsd-x64@0.27.0': 559 + resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} 538 560 engines: {node: '>=18'} 539 561 cpu: [x64] 540 562 os: [openbsd] 541 563 542 - '@esbuild/sunos-x64@0.24.2': 543 - resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} 564 + '@esbuild/openharmony-arm64@0.25.12': 565 + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} 566 + engines: {node: '>=18'} 567 + cpu: [arm64] 568 + os: [openharmony] 569 + 570 + '@esbuild/openharmony-arm64@0.27.0': 571 + resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} 572 + engines: {node: '>=18'} 573 + cpu: [arm64] 574 + os: [openharmony] 575 + 576 + '@esbuild/sunos-x64@0.25.12': 577 + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} 544 578 engines: {node: '>=18'} 545 579 cpu: [x64] 546 580 os: [sunos] 547 581 548 - '@esbuild/sunos-x64@0.25.1': 549 - resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} 582 + '@esbuild/sunos-x64@0.27.0': 583 + resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} 550 584 engines: {node: '>=18'} 551 585 cpu: [x64] 552 586 os: [sunos] 553 587 554 - '@esbuild/win32-arm64@0.24.2': 555 - resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} 588 + '@esbuild/win32-arm64@0.25.12': 589 + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} 556 590 engines: {node: '>=18'} 557 591 cpu: [arm64] 558 592 os: [win32] 559 593 560 - '@esbuild/win32-arm64@0.25.1': 561 - resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} 594 + '@esbuild/win32-arm64@0.27.0': 595 + resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} 562 596 engines: {node: '>=18'} 563 597 cpu: [arm64] 564 598 os: [win32] 565 599 566 - '@esbuild/win32-ia32@0.24.2': 567 - resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} 600 + '@esbuild/win32-ia32@0.25.12': 601 + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} 568 602 engines: {node: '>=18'} 569 603 cpu: [ia32] 570 604 os: [win32] 571 605 572 - '@esbuild/win32-ia32@0.25.1': 573 - resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} 606 + '@esbuild/win32-ia32@0.27.0': 607 + resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} 574 608 engines: {node: '>=18'} 575 609 cpu: [ia32] 576 610 os: [win32] 577 611 578 - '@esbuild/win32-x64@0.24.2': 579 - resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} 612 + '@esbuild/win32-x64@0.25.12': 613 + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} 580 614 engines: {node: '>=18'} 581 615 cpu: [x64] 582 616 os: [win32] 583 617 584 - '@esbuild/win32-x64@0.25.1': 585 - resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} 618 + '@esbuild/win32-x64@0.27.0': 619 + resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} 586 620 engines: {node: '>=18'} 587 621 cpu: [x64] 588 622 os: [win32] ··· 591 625 resolution: {integrity: sha512-duvZBfJB9oOLphx04ckKF534hP186xIBFaw4GHJ5fGeZY5syZs59UeumV5NC6aiEU9hVhAFMOnDDGkQrFqHrnQ==} 592 626 peerDependencies: 593 627 solid-js: ^1.8.5 594 - 595 - '@fastify/busboy@2.1.1': 596 - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} 597 - engines: {node: '>=14'} 598 628 599 629 '@img/sharp-darwin-arm64@0.33.5': 600 630 resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} ··· 701 731 cpu: [x64] 702 732 os: [win32] 703 733 704 - '@isaacs/cliui@8.0.2': 705 - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 706 - engines: {node: '>=12'} 734 + '@jridgewell/gen-mapping@0.3.13': 735 + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} 707 736 708 - '@jridgewell/gen-mapping@0.3.8': 709 - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} 710 - engines: {node: '>=6.0.0'} 737 + '@jridgewell/remapping@2.3.5': 738 + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} 711 739 712 740 '@jridgewell/resolve-uri@3.1.2': 713 741 resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 714 742 engines: {node: '>=6.0.0'} 715 743 716 - '@jridgewell/set-array@1.2.1': 717 - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 718 - engines: {node: '>=6.0.0'} 744 + '@jridgewell/source-map@0.3.11': 745 + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} 719 746 720 - '@jridgewell/source-map@0.3.6': 721 - resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} 747 + '@jridgewell/sourcemap-codec@1.5.5': 748 + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} 722 749 723 - '@jridgewell/sourcemap-codec@1.5.0': 724 - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 725 - 726 - '@jridgewell/trace-mapping@0.3.25': 727 - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 750 + '@jridgewell/trace-mapping@0.3.31': 751 + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} 728 752 729 753 '@jridgewell/trace-mapping@0.3.9': 730 754 resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} 731 755 732 - '@jsr/mary__array-fns@0.1.4': 733 - resolution: {integrity: sha512-+HbGYR9Ll5blEmAvVAoPejyGj01YeBbVmJ59qxaMDKt5i3F90ohYLA5a78y6AULDlet1IxYB+a/cMN+A0vGnDg==, tarball: https://npm.jsr.io/~/11/@jsr/mary__array-fns/0.1.4.tgz} 756 + '@jsr/mary__array-fns@0.1.5': 757 + resolution: {integrity: sha512-gI4scq/Hh9GtFUJfS8cvZf5nr+cs7udvrEpMv75grws5/0LIwBycKeeJcNi4+xNl6x4CGW6Fp46puhtJiQOpMg==, tarball: https://npm.jsr.io/~/11/@jsr/mary__array-fns/0.1.5.tgz} 758 + 759 + '@jsr/mary__ds-queue@0.1.3': 760 + resolution: {integrity: sha512-gGqIHXiAmhUUtonNI6YVvL7VlXjEHUpGdc7RGU8BLP4XnFvqovDTH5y9VlBZmvozTWgTIMoZF6/1//sMrvYKtQ==, tarball: https://npm.jsr.io/~/11/@jsr/mary__ds-queue/0.1.3.tgz} 734 761 735 - '@jsr/mary__events@0.1.0': 736 - resolution: {integrity: sha512-oS6jVOaXTaNEa6avRncwrEtUYaBKrq/HEybPa9Z3aoeMs+RSly0vn0KcOj/fy2H6iTBkeh3wa8+/9nFjhKyKIg==, tarball: https://npm.jsr.io/~/11/@jsr/mary__events/0.1.0.tgz} 762 + '@jsr/mary__events@0.2.0': 763 + resolution: {integrity: sha512-WcBRbtuTno3zcfXKd7SEeKr1lAJF+CQ8BCv+PEEMmNKNqFurkEksGxRB3UDPZxIxjJ7sAqMVTL26wRuMpAcIeA==, tarball: https://npm.jsr.io/~/11/@jsr/mary__events/0.2.0.tgz} 737 764 738 - '@jsr/mary__tar@0.2.4': 739 - resolution: {integrity: sha512-jFjPcZj8DRSukPLZOt6+h74cVFdfdTMG9gzbW67YByCJTD52PEpe2sNcfCSw4mQ8hZBNgwiufCPyYL8hR9yicA==, tarball: https://npm.jsr.io/~/11/@jsr/mary__tar/0.2.4.tgz} 765 + '@jsr/mary__tar@0.3.1': 766 + resolution: {integrity: sha512-T803kucwCLVOXFJGzVbpkT5vRK6fARy5HL6xMiLK5hJFck72bsAeluENlRnvD0kFPSlFNp/5EJWfTHnpDK0qYA==, tarball: https://npm.jsr.io/~/11/@jsr/mary__tar/0.3.1.tgz} 740 767 741 - '@noble/secp256k1@2.2.3': 742 - resolution: {integrity: sha512-l7r5oEQym9Us7EAigzg30/PQAvynhMt2uoYtT3t26eGDVm9Yii5mZ5jWSWmZ/oSIR2Et0xfc6DXrG0bZ787V3w==} 768 + '@noble/secp256k1@3.0.0': 769 + resolution: {integrity: sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==} 743 770 744 771 '@nodelib/fs.scandir@2.1.5': 745 772 resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} ··· 753 780 resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 754 781 engines: {node: '>= 8'} 755 782 756 - '@pkgjs/parseargs@0.11.0': 757 - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 758 - engines: {node: '>=14'} 783 + '@poppinss/colors@4.1.5': 784 + resolution: {integrity: sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==} 785 + 786 + '@poppinss/dumper@0.6.5': 787 + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} 788 + 789 + '@poppinss/exception@1.2.2': 790 + resolution: {integrity: sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==} 759 791 760 - '@rollup/rollup-android-arm-eabi@4.37.0': 761 - resolution: {integrity: sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ==} 792 + '@rollup/rollup-android-arm-eabi@4.53.3': 793 + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} 762 794 cpu: [arm] 763 795 os: [android] 764 796 765 - '@rollup/rollup-android-arm64@4.37.0': 766 - resolution: {integrity: sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA==} 797 + '@rollup/rollup-android-arm64@4.53.3': 798 + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} 767 799 cpu: [arm64] 768 800 os: [android] 769 801 770 - '@rollup/rollup-darwin-arm64@4.37.0': 771 - resolution: {integrity: sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA==} 802 + '@rollup/rollup-darwin-arm64@4.53.3': 803 + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} 772 804 cpu: [arm64] 773 805 os: [darwin] 774 806 775 - '@rollup/rollup-darwin-x64@4.37.0': 776 - resolution: {integrity: sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ==} 807 + '@rollup/rollup-darwin-x64@4.53.3': 808 + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} 777 809 cpu: [x64] 778 810 os: [darwin] 779 811 780 - '@rollup/rollup-freebsd-arm64@4.37.0': 781 - resolution: {integrity: sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA==} 812 + '@rollup/rollup-freebsd-arm64@4.53.3': 813 + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} 782 814 cpu: [arm64] 783 815 os: [freebsd] 784 816 785 - '@rollup/rollup-freebsd-x64@4.37.0': 786 - resolution: {integrity: sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA==} 817 + '@rollup/rollup-freebsd-x64@4.53.3': 818 + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} 787 819 cpu: [x64] 788 820 os: [freebsd] 789 821 790 - '@rollup/rollup-linux-arm-gnueabihf@4.37.0': 791 - resolution: {integrity: sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w==} 822 + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': 823 + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} 792 824 cpu: [arm] 793 825 os: [linux] 794 826 795 - '@rollup/rollup-linux-arm-musleabihf@4.37.0': 796 - resolution: {integrity: sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag==} 827 + '@rollup/rollup-linux-arm-musleabihf@4.53.3': 828 + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} 797 829 cpu: [arm] 798 830 os: [linux] 799 831 800 - '@rollup/rollup-linux-arm64-gnu@4.37.0': 801 - resolution: {integrity: sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA==} 832 + '@rollup/rollup-linux-arm64-gnu@4.53.3': 833 + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} 802 834 cpu: [arm64] 803 835 os: [linux] 804 836 805 - '@rollup/rollup-linux-arm64-musl@4.37.0': 806 - resolution: {integrity: sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ==} 837 + '@rollup/rollup-linux-arm64-musl@4.53.3': 838 + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} 807 839 cpu: [arm64] 808 840 os: [linux] 809 841 810 - '@rollup/rollup-linux-loongarch64-gnu@4.37.0': 811 - resolution: {integrity: sha512-yCE0NnutTC/7IGUq/PUHmoeZbIwq3KRh02e9SfFh7Vmc1Z7atuJRYWhRME5fKgT8aS20mwi1RyChA23qSyRGpA==} 842 + '@rollup/rollup-linux-loong64-gnu@4.53.3': 843 + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} 812 844 cpu: [loong64] 813 845 os: [linux] 814 846 815 - '@rollup/rollup-linux-powerpc64le-gnu@4.37.0': 816 - resolution: {integrity: sha512-NxcICptHk06E2Lh3a4Pu+2PEdZ6ahNHuK7o6Np9zcWkrBMuv21j10SQDJW3C9Yf/A/P7cutWoC/DptNLVsZ0VQ==} 847 + '@rollup/rollup-linux-ppc64-gnu@4.53.3': 848 + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} 817 849 cpu: [ppc64] 818 850 os: [linux] 819 851 820 - '@rollup/rollup-linux-riscv64-gnu@4.37.0': 821 - resolution: {integrity: sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw==} 852 + '@rollup/rollup-linux-riscv64-gnu@4.53.3': 853 + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} 822 854 cpu: [riscv64] 823 855 os: [linux] 824 856 825 - '@rollup/rollup-linux-riscv64-musl@4.37.0': 826 - resolution: {integrity: sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA==} 857 + '@rollup/rollup-linux-riscv64-musl@4.53.3': 858 + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} 827 859 cpu: [riscv64] 828 860 os: [linux] 829 861 830 - '@rollup/rollup-linux-s390x-gnu@4.37.0': 831 - resolution: {integrity: sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A==} 862 + '@rollup/rollup-linux-s390x-gnu@4.53.3': 863 + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} 832 864 cpu: [s390x] 833 865 os: [linux] 834 866 835 - '@rollup/rollup-linux-x64-gnu@4.37.0': 836 - resolution: {integrity: sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ==} 867 + '@rollup/rollup-linux-x64-gnu@4.53.3': 868 + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} 837 869 cpu: [x64] 838 870 os: [linux] 839 871 840 - '@rollup/rollup-linux-x64-musl@4.37.0': 841 - resolution: {integrity: sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w==} 872 + '@rollup/rollup-linux-x64-musl@4.53.3': 873 + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} 842 874 cpu: [x64] 843 875 os: [linux] 844 876 845 - '@rollup/rollup-win32-arm64-msvc@4.37.0': 846 - resolution: {integrity: sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg==} 877 + '@rollup/rollup-openharmony-arm64@4.53.3': 878 + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} 879 + cpu: [arm64] 880 + os: [openharmony] 881 + 882 + '@rollup/rollup-win32-arm64-msvc@4.53.3': 883 + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} 847 884 cpu: [arm64] 848 885 os: [win32] 849 886 850 - '@rollup/rollup-win32-ia32-msvc@4.37.0': 851 - resolution: {integrity: sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA==} 887 + '@rollup/rollup-win32-ia32-msvc@4.53.3': 888 + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} 852 889 cpu: [ia32] 853 890 os: [win32] 854 891 855 - '@rollup/rollup-win32-x64-msvc@4.37.0': 856 - resolution: {integrity: sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA==} 892 + '@rollup/rollup-win32-x64-gnu@4.53.3': 893 + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} 894 + cpu: [x64] 895 + os: [win32] 896 + 897 + '@rollup/rollup-win32-x64-msvc@4.53.3': 898 + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} 857 899 cpu: [x64] 858 900 os: [win32] 859 901 902 + '@sindresorhus/is@7.1.1': 903 + resolution: {integrity: sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==} 904 + engines: {node: '>=18'} 905 + 906 + '@speed-highlight/core@1.2.12': 907 + resolution: {integrity: sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA==} 908 + 909 + '@standard-schema/spec@1.0.0': 910 + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} 911 + 860 912 '@tailwindcss/forms@0.5.10': 861 913 resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==} 862 914 peerDependencies: ··· 865 917 '@types/babel__core@7.20.5': 866 918 resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} 867 919 868 - '@types/babel__generator@7.6.8': 869 - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} 920 + '@types/babel__generator@7.27.0': 921 + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} 870 922 871 923 '@types/babel__template@7.4.4': 872 924 resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} 873 925 874 - '@types/babel__traverse@7.20.6': 875 - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} 926 + '@types/babel__traverse@7.28.0': 927 + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} 876 928 877 - '@types/estree@1.0.6': 878 - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 929 + '@types/estree@1.0.8': 930 + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 879 931 880 - '@types/node@22.13.12': 881 - resolution: {integrity: sha512-ixiWrCSRi33uqBMRuICcKECW7rtgY43TbsHDpM2XK7lXispd48opW+0IXrBVxv9NMhaz/Ue9kyj6r3NTVyXm8A==} 932 + '@types/node@22.19.2': 933 + resolution: {integrity: sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==} 882 934 883 935 acorn-walk@8.3.2: 884 936 resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} ··· 889 941 engines: {node: '>=0.4.0'} 890 942 hasBin: true 891 943 892 - acorn@8.14.1: 893 - resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} 944 + acorn@8.15.0: 945 + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} 894 946 engines: {node: '>=0.4.0'} 895 947 hasBin: true 896 948 897 - ansi-regex@5.0.1: 898 - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 899 - engines: {node: '>=8'} 900 - 901 - ansi-regex@6.1.0: 902 - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} 903 - engines: {node: '>=12'} 904 - 905 - ansi-styles@4.3.0: 906 - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 907 - engines: {node: '>=8'} 908 - 909 - ansi-styles@6.2.1: 910 - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 911 - engines: {node: '>=12'} 912 - 913 949 any-promise@1.3.0: 914 950 resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} 915 951 ··· 920 956 arg@5.0.2: 921 957 resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} 922 958 923 - as-table@1.0.55: 924 - resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==} 925 - 926 - autoprefixer@10.4.21: 927 - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} 959 + autoprefixer@10.4.22: 960 + resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==} 928 961 engines: {node: ^10 || ^12 || >=14} 929 962 hasBin: true 930 963 peerDependencies: 931 964 postcss: ^8.1.0 932 965 933 - babel-plugin-jsx-dom-expressions@0.39.7: 934 - resolution: {integrity: sha512-8GzVmFla7jaTNWW8W+lTMl9YGva4/06CtwJjySnkYtt8G1v9weCzc2SuF1DfrudcCNb2Doetc1FRg33swBYZCA==} 966 + babel-plugin-jsx-dom-expressions@0.40.3: 967 + resolution: {integrity: sha512-5HOwwt0BYiv/zxl7j8Pf2bGL6rDXfV6nUhLs8ygBX+EFJXzBPHM/euj9j/6deMZ6wa52Wb2PBaAV5U/jKwIY1w==} 935 968 peerDependencies: 936 969 '@babel/core': ^7.20.12 937 970 938 - babel-preset-solid@1.9.5: 939 - resolution: {integrity: sha512-85I3osODJ1LvZbv8wFozROV1vXq32BubqHXAGu73A//TRs3NLI1OFP83AQBUTSQHwgZQmARjHlJciym3we+V+w==} 971 + babel-preset-solid@1.9.10: 972 + resolution: {integrity: sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ==} 940 973 peerDependencies: 941 974 '@babel/core': ^7.0.0 975 + solid-js: ^1.9.10 976 + peerDependenciesMeta: 977 + solid-js: 978 + optional: true 942 979 943 - balanced-match@1.0.2: 944 - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 980 + baseline-browser-mapping@2.9.5: 981 + resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==} 982 + hasBin: true 945 983 946 984 binary-extensions@2.3.0: 947 985 resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} ··· 949 987 950 988 blake3-wasm@2.1.5: 951 989 resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} 952 - 953 - brace-expansion@2.0.1: 954 - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 955 990 956 991 braces@3.0.3: 957 992 resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 958 993 engines: {node: '>=8'} 959 994 960 - browserslist@4.24.4: 961 - resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} 995 + browserslist@4.28.1: 996 + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} 962 997 engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 963 998 hasBin: true 964 999 ··· 969 1004 resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} 970 1005 engines: {node: '>= 6'} 971 1006 972 - caniuse-lite@1.0.30001707: 973 - resolution: {integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==} 1007 + caniuse-lite@1.0.30001760: 1008 + resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} 974 1009 975 1010 chokidar@3.6.0: 976 1011 resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} ··· 1000 1035 convert-source-map@2.0.0: 1001 1036 resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} 1002 1037 1003 - cookie@0.5.0: 1004 - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} 1005 - engines: {node: '>= 0.6'} 1006 - 1007 - cross-spawn@7.0.6: 1008 - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 1009 - engines: {node: '>= 8'} 1038 + cookie@1.1.1: 1039 + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} 1040 + engines: {node: '>=18'} 1010 1041 1011 1042 cssesc@3.0.0: 1012 1043 resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 1013 1044 engines: {node: '>=4'} 1014 1045 hasBin: true 1015 1046 1016 - csstype@3.1.3: 1017 - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 1047 + csstype@3.2.3: 1048 + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} 1018 1049 1019 - data-uri-to-buffer@2.0.2: 1020 - resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} 1021 - 1022 - debug@4.4.0: 1023 - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 1050 + debug@4.4.3: 1051 + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} 1024 1052 engines: {node: '>=6.0'} 1025 1053 peerDependencies: 1026 1054 supports-color: '*' ··· 1028 1056 supports-color: 1029 1057 optional: true 1030 1058 1031 - defu@6.1.4: 1032 - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 1033 - 1034 - detect-libc@2.0.3: 1035 - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} 1059 + detect-libc@2.1.2: 1060 + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} 1036 1061 engines: {node: '>=8'} 1037 1062 1038 1063 didyoumean@1.2.2: ··· 1041 1066 dlv@1.1.3: 1042 1067 resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} 1043 1068 1044 - eastasianwidth@0.2.0: 1045 - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 1069 + electron-to-chromium@1.5.267: 1070 + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} 1046 1071 1047 - electron-to-chromium@1.5.123: 1048 - resolution: {integrity: sha512-refir3NlutEZqlKaBLK0tzlVLe5P2wDKS7UQt/3SpibizgsRAPOsqQC3ffw1nlv3ze5gjRQZYHoPymgVZkplFA==} 1072 + entities@6.0.1: 1073 + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} 1074 + engines: {node: '>=0.12'} 1049 1075 1050 - emoji-regex@8.0.0: 1051 - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 1052 - 1053 - emoji-regex@9.2.2: 1054 - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 1076 + error-stack-parser-es@1.0.5: 1077 + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} 1055 1078 1056 - entities@4.5.0: 1057 - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} 1058 - engines: {node: '>=0.12'} 1059 - 1060 - esbuild@0.24.2: 1061 - resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} 1079 + esbuild@0.25.12: 1080 + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} 1062 1081 engines: {node: '>=18'} 1063 1082 hasBin: true 1064 1083 1065 - esbuild@0.25.1: 1066 - resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} 1084 + esbuild@0.27.0: 1085 + resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} 1067 1086 engines: {node: '>=18'} 1068 1087 hasBin: true 1069 1088 ··· 1071 1090 resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 1072 1091 engines: {node: '>=6'} 1073 1092 1093 + esm-env@1.2.2: 1094 + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} 1095 + 1074 1096 exit-hook@2.2.1: 1075 1097 resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} 1076 1098 engines: {node: '>=6'} 1077 - 1078 - exsolve@1.0.4: 1079 - resolution: {integrity: sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==} 1080 1099 1081 1100 fast-glob@3.3.3: 1082 1101 resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} ··· 1085 1104 fastq@1.19.1: 1086 1105 resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} 1087 1106 1107 + fdir@6.5.0: 1108 + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} 1109 + engines: {node: '>=12.0.0'} 1110 + peerDependencies: 1111 + picomatch: ^3 || ^4 1112 + peerDependenciesMeta: 1113 + picomatch: 1114 + optional: true 1115 + 1088 1116 fetch-blob@3.2.0: 1089 1117 resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} 1090 1118 engines: {node: ^12.20 || >= 14.13} ··· 1093 1121 resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 1094 1122 engines: {node: '>=8'} 1095 1123 1096 - foreground-child@3.3.1: 1097 - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} 1098 - engines: {node: '>=14'} 1099 - 1100 - fraction.js@4.3.7: 1101 - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} 1124 + fraction.js@5.3.4: 1125 + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} 1102 1126 1103 1127 fsevents@2.3.3: 1104 1128 resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} ··· 1111 1135 gensync@1.0.0-beta.2: 1112 1136 resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} 1113 1137 engines: {node: '>=6.9.0'} 1114 - 1115 - get-source@2.0.12: 1116 - resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} 1117 1138 1118 1139 glob-parent@5.1.2: 1119 1140 resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} ··· 1126 1147 glob-to-regexp@0.4.1: 1127 1148 resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} 1128 1149 1129 - glob@10.4.5: 1130 - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 1131 - hasBin: true 1132 - 1133 - globals@11.12.0: 1134 - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} 1135 - engines: {node: '>=4'} 1136 - 1137 1150 hasown@2.0.2: 1138 1151 resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 1139 1152 engines: {node: '>= 0.4'} ··· 1141 1154 html-entities@2.3.3: 1142 1155 resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} 1143 1156 1144 - is-arrayish@0.3.2: 1145 - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} 1157 + is-arrayish@0.3.4: 1158 + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} 1146 1159 1147 1160 is-binary-path@2.1.0: 1148 1161 resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} ··· 1156 1169 resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 1157 1170 engines: {node: '>=0.10.0'} 1158 1171 1159 - is-fullwidth-code-point@3.0.0: 1160 - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 1161 - engines: {node: '>=8'} 1162 - 1163 1172 is-glob@4.0.3: 1164 1173 resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 1165 1174 engines: {node: '>=0.10.0'} ··· 1172 1181 resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} 1173 1182 engines: {node: '>=12.13'} 1174 1183 1175 - isexe@2.0.0: 1176 - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1177 - 1178 - jackspeak@3.4.3: 1179 - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 1180 - 1181 1184 jiti@1.21.7: 1182 1185 resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} 1183 1186 hasBin: true ··· 1195 1198 engines: {node: '>=6'} 1196 1199 hasBin: true 1197 1200 1201 + kleur@4.1.5: 1202 + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} 1203 + engines: {node: '>=6'} 1204 + 1198 1205 lilconfig@3.1.3: 1199 1206 resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} 1200 1207 engines: {node: '>=14'} ··· 1202 1209 lines-and-columns@1.2.4: 1203 1210 resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 1204 1211 1205 - lru-cache@10.4.3: 1206 - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 1207 - 1208 1212 lru-cache@5.1.1: 1209 1213 resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} 1210 1214 ··· 1229 1233 resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} 1230 1234 hasBin: true 1231 1235 1232 - miniflare@4.20250320.0: 1233 - resolution: {integrity: sha512-dD9gpO/nWaLURbBXctB/FOJEDexPlSbplIApb5Ea3xGuSSh+3Iq/cfbgh3IdgueIGMJb6vvTiOWpiPA5naX6vg==} 1236 + miniflare@4.20251202.1: 1237 + resolution: {integrity: sha512-cRp2QNgnt9wpLMoNs4MOzzomyfe9UTS9sPRxIpUvxMl+mweCZ0FHpWWQvCnU7wWlfAP8VGZrHwqSsV5ERA6ahQ==} 1234 1238 engines: {node: '>=18.0.0'} 1235 1239 hasBin: true 1236 1240 1237 - minimatch@9.0.5: 1238 - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 1239 - engines: {node: '>=16 || 14 >=14.17'} 1240 - 1241 - minipass@7.1.2: 1242 - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 1243 - engines: {node: '>=16 || 14 >=14.17'} 1244 - 1245 1241 ms@2.1.3: 1246 1242 resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1247 1243 1248 - mustache@4.2.0: 1249 - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} 1250 - hasBin: true 1251 - 1252 1244 mz@2.7.0: 1253 1245 resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} 1254 1246 ··· 1257 1249 engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 1258 1250 hasBin: true 1259 1251 1260 - nanoid@5.1.5: 1261 - resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} 1252 + nanoid@5.1.6: 1253 + resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} 1262 1254 engines: {node: ^18 || >=20} 1263 1255 hasBin: true 1264 1256 ··· 1269 1261 node-domexception@1.0.0: 1270 1262 resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} 1271 1263 engines: {node: '>=10.5.0'} 1264 + deprecated: Use your platform's native DOMException instead 1272 1265 1273 - node-releases@2.0.19: 1274 - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} 1266 + node-releases@2.0.27: 1267 + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} 1275 1268 1276 1269 normalize-path@3.0.0: 1277 1270 resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} ··· 1289 1282 resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} 1290 1283 engines: {node: '>= 6'} 1291 1284 1292 - ohash@2.0.11: 1293 - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} 1294 - 1295 - package-json-from-dist@1.0.1: 1296 - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 1297 - 1298 - parse5@7.2.1: 1299 - resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} 1300 - 1301 - path-key@3.1.1: 1302 - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1303 - engines: {node: '>=8'} 1285 + parse5@7.3.0: 1286 + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} 1304 1287 1305 1288 path-parse@1.0.7: 1306 1289 resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1307 1290 1308 - path-scurry@1.11.1: 1309 - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 1310 - engines: {node: '>=16 || 14 >=14.18'} 1311 - 1312 1291 path-to-regexp@6.3.0: 1313 1292 resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} 1314 1293 ··· 1322 1301 resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1323 1302 engines: {node: '>=8.6'} 1324 1303 1304 + picomatch@4.0.3: 1305 + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} 1306 + engines: {node: '>=12'} 1307 + 1325 1308 pify@2.3.0: 1326 1309 resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} 1327 1310 engines: {node: '>=0.10.0'} 1328 1311 1329 - pirates@4.0.6: 1330 - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} 1312 + pirates@4.0.7: 1313 + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} 1331 1314 engines: {node: '>= 6'} 1332 1315 1333 1316 postcss-import@15.1.0: ··· 1336 1319 peerDependencies: 1337 1320 postcss: ^8.0.0 1338 1321 1339 - postcss-js@4.0.1: 1340 - resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} 1322 + postcss-js@4.1.0: 1323 + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} 1341 1324 engines: {node: ^12 || ^14 || >= 16} 1342 1325 peerDependencies: 1343 1326 postcss: ^8.4.21 1344 1327 1345 - postcss-load-config@4.0.2: 1346 - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} 1347 - engines: {node: '>= 14'} 1328 + postcss-load-config@6.0.1: 1329 + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} 1330 + engines: {node: '>= 18'} 1348 1331 peerDependencies: 1332 + jiti: '>=1.21.0' 1349 1333 postcss: '>=8.0.9' 1350 - ts-node: '>=9.0.0' 1334 + tsx: ^4.8.1 1335 + yaml: ^2.4.2 1351 1336 peerDependenciesMeta: 1337 + jiti: 1338 + optional: true 1352 1339 postcss: 1353 1340 optional: true 1354 - ts-node: 1341 + tsx: 1342 + optional: true 1343 + yaml: 1355 1344 optional: true 1356 1345 1357 1346 postcss-nested@6.2.0: ··· 1367 1356 postcss-value-parser@4.2.0: 1368 1357 resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} 1369 1358 1370 - postcss@8.5.3: 1371 - resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} 1359 + postcss@8.5.6: 1360 + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 1372 1361 engines: {node: ^10 || ^12 || >=14} 1373 1362 1374 - prettier-plugin-tailwindcss@0.6.11: 1375 - resolution: {integrity: sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==} 1363 + prettier-plugin-tailwindcss@0.6.14: 1364 + resolution: {integrity: sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==} 1376 1365 engines: {node: '>=14.21.3'} 1377 1366 peerDependencies: 1378 1367 '@ianvs/prettier-plugin-sort-imports': '*' 1368 + '@prettier/plugin-hermes': '*' 1369 + '@prettier/plugin-oxc': '*' 1379 1370 '@prettier/plugin-pug': '*' 1380 1371 '@shopify/prettier-plugin-liquid': '*' 1381 1372 '@trivago/prettier-plugin-sort-imports': '*' ··· 1394 1385 prettier-plugin-svelte: '*' 1395 1386 peerDependenciesMeta: 1396 1387 '@ianvs/prettier-plugin-sort-imports': 1388 + optional: true 1389 + '@prettier/plugin-hermes': 1390 + optional: true 1391 + '@prettier/plugin-oxc': 1397 1392 optional: true 1398 1393 '@prettier/plugin-pug': 1399 1394 optional: true ··· 1426 1421 prettier-plugin-svelte: 1427 1422 optional: true 1428 1423 1429 - prettier@3.5.3: 1430 - resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} 1424 + prettier@3.7.4: 1425 + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} 1431 1426 engines: {node: '>=14'} 1432 1427 hasBin: true 1433 - 1434 - printable-characters@1.0.42: 1435 - resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} 1436 1428 1437 1429 queue-microtask@1.2.3: 1438 1430 resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} ··· 1444 1436 resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 1445 1437 engines: {node: '>=8.10.0'} 1446 1438 1447 - resolve@1.22.10: 1448 - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} 1439 + resolve@1.22.11: 1440 + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} 1449 1441 engines: {node: '>= 0.4'} 1450 1442 hasBin: true 1451 1443 ··· 1453 1445 resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} 1454 1446 engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1455 1447 1456 - rollup@4.37.0: 1457 - resolution: {integrity: sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg==} 1448 + rollup@4.53.3: 1449 + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} 1458 1450 engines: {node: '>=18.0.0', npm: '>=8.0.0'} 1459 1451 hasBin: true 1460 1452 ··· 1465 1457 resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 1466 1458 hasBin: true 1467 1459 1468 - semver@7.7.1: 1469 - resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} 1460 + semver@7.7.3: 1461 + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} 1470 1462 engines: {node: '>=10'} 1471 1463 hasBin: true 1472 1464 1473 - seroval-plugins@1.2.1: 1474 - resolution: {integrity: sha512-H5vs53+39+x4Udwp4J5rNZfgFuA+Lt+uU+09w1gYBVWomtAl98B+E9w7yC05Xc81/HgLvJdlyqJbU0fJCKCmdw==} 1465 + seroval-plugins@1.3.3: 1466 + resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==} 1475 1467 engines: {node: '>=10'} 1476 1468 peerDependencies: 1477 1469 seroval: ^1.0 1478 1470 1479 - seroval@1.2.1: 1480 - resolution: {integrity: sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw==} 1471 + seroval@1.3.2: 1472 + resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} 1481 1473 engines: {node: '>=10'} 1482 1474 1483 1475 sharp@0.33.5: 1484 1476 resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} 1485 1477 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 1486 1478 1487 - shebang-command@2.0.0: 1488 - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1489 - engines: {node: '>=8'} 1490 - 1491 - shebang-regex@3.0.0: 1492 - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1493 - engines: {node: '>=8'} 1479 + simple-swizzle@0.2.4: 1480 + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} 1494 1481 1495 - signal-exit@4.1.0: 1496 - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 1497 - engines: {node: '>=14'} 1498 - 1499 - simple-swizzle@0.2.2: 1500 - resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} 1501 - 1502 - solid-js@1.9.5: 1503 - resolution: {integrity: sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw==} 1482 + solid-js@1.9.10: 1483 + resolution: {integrity: sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==} 1504 1484 1505 1485 solid-refresh@0.6.3: 1506 1486 resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==} ··· 1518 1498 resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 1519 1499 engines: {node: '>=0.10.0'} 1520 1500 1521 - stacktracey@2.1.8: 1522 - resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==} 1523 - 1524 1501 stoppable@1.1.0: 1525 1502 resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} 1526 1503 engines: {node: '>=4', npm: '>=6'} 1527 1504 1528 - string-width@4.2.3: 1529 - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 1530 - engines: {node: '>=8'} 1531 - 1532 - string-width@5.1.2: 1533 - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 1534 - engines: {node: '>=12'} 1535 - 1536 - strip-ansi@6.0.1: 1537 - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1538 - engines: {node: '>=8'} 1539 - 1540 - strip-ansi@7.1.0: 1541 - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 1542 - engines: {node: '>=12'} 1543 - 1544 - sucrase@3.35.0: 1545 - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} 1505 + sucrase@3.35.1: 1506 + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} 1546 1507 engines: {node: '>=16 || 14 >=14.17'} 1547 1508 hasBin: true 1509 + 1510 + supports-color@10.2.2: 1511 + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} 1512 + engines: {node: '>=18'} 1548 1513 1549 1514 supports-preserve-symlinks-flag@1.0.0: 1550 1515 resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1551 1516 engines: {node: '>= 0.4'} 1552 1517 1553 - tailwindcss@3.4.17: 1554 - resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} 1518 + tailwindcss@3.4.18: 1519 + resolution: {integrity: sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==} 1555 1520 engines: {node: '>=14.0.0'} 1556 1521 hasBin: true 1557 1522 1558 - terser@5.39.0: 1559 - resolution: {integrity: sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==} 1523 + terser@5.44.1: 1524 + resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} 1560 1525 engines: {node: '>=10'} 1561 1526 hasBin: true 1562 1527 ··· 1567 1532 thenify@3.3.1: 1568 1533 resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} 1569 1534 1535 + tinyglobby@0.2.15: 1536 + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} 1537 + engines: {node: '>=12.0.0'} 1538 + 1570 1539 to-regex-range@5.0.1: 1571 1540 resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1572 1541 engines: {node: '>=8.0'} ··· 1577 1546 tslib@2.8.1: 1578 1547 resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1579 1548 1580 - typescript@5.8.2: 1581 - resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} 1549 + typescript@5.9.3: 1550 + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} 1582 1551 engines: {node: '>=14.17'} 1583 1552 hasBin: true 1584 1553 1585 - ufo@1.5.4: 1586 - resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} 1554 + undici-types@6.21.0: 1555 + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 1587 1556 1588 - undici-types@6.20.0: 1589 - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} 1557 + undici@7.14.0: 1558 + resolution: {integrity: sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==} 1559 + engines: {node: '>=20.18.1'} 1590 1560 1591 - undici@5.29.0: 1592 - resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} 1593 - engines: {node: '>=14.0'} 1594 - 1595 - unenv@2.0.0-rc.15: 1596 - resolution: {integrity: sha512-J/rEIZU8w6FOfLNz/hNKsnY+fFHWnu9MH4yRbSZF3xbbGHovcetXPs7sD+9p8L6CeNC//I9bhRYAOsBt2u7/OA==} 1561 + unenv@2.0.0-rc.24: 1562 + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} 1597 1563 1598 - update-browserslist-db@1.1.3: 1599 - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} 1564 + update-browserslist-db@1.2.2: 1565 + resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} 1600 1566 hasBin: true 1601 1567 peerDependencies: 1602 1568 browserslist: '>= 4.21.0' ··· 1604 1570 util-deprecate@1.0.2: 1605 1571 resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1606 1572 1607 - validate-html-nesting@1.2.2: 1608 - resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==} 1609 - 1610 - vite-plugin-solid@2.11.6: 1611 - resolution: {integrity: sha512-Sl5CTqJTGyEeOsmdH6BOgalIZlwH3t4/y0RQuFLMGnvWMBvxb4+lq7x3BSiAw6etf0QexfNJW7HSOO/Qf7pigg==} 1573 + vite-plugin-solid@2.11.10: 1574 + resolution: {integrity: sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw==} 1612 1575 peerDependencies: 1613 1576 '@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.* 1614 1577 solid-js: ^1.7.2 1615 - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 1578 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 1616 1579 peerDependenciesMeta: 1617 1580 '@testing-library/jest-dom': 1618 1581 optional: true 1619 1582 1620 - vite@6.2.2: 1621 - resolution: {integrity: sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==} 1622 - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 1583 + vite@7.2.7: 1584 + resolution: {integrity: sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==} 1585 + engines: {node: ^20.19.0 || >=22.12.0} 1623 1586 hasBin: true 1624 1587 peerDependencies: 1625 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 1588 + '@types/node': ^20.19.0 || >=22.12.0 1626 1589 jiti: '>=1.21.0' 1627 - less: '*' 1590 + less: ^4.0.0 1628 1591 lightningcss: ^1.21.0 1629 - sass: '*' 1630 - sass-embedded: '*' 1631 - stylus: '*' 1632 - sugarss: '*' 1592 + sass: ^1.70.0 1593 + sass-embedded: ^1.70.0 1594 + stylus: '>=0.54.8' 1595 + sugarss: ^5.0.0 1633 1596 terser: ^5.16.0 1634 1597 tsx: ^4.8.1 1635 1598 yaml: ^2.4.2 ··· 1657 1620 yaml: 1658 1621 optional: true 1659 1622 1660 - vitefu@1.0.6: 1661 - resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==} 1623 + vitefu@1.1.1: 1624 + resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} 1662 1625 peerDependencies: 1663 - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 1626 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 1664 1627 peerDependenciesMeta: 1665 1628 vite: 1666 1629 optional: true ··· 1669 1632 resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} 1670 1633 engines: {node: '>= 8'} 1671 1634 1672 - which@2.0.2: 1673 - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1674 - engines: {node: '>= 8'} 1675 - hasBin: true 1676 - 1677 - workerd@1.20250320.0: 1678 - resolution: {integrity: sha512-XrjREboPo1AZNF3kSEly/H1Ejmpu2Mk/Wzsxprn7MHUmBnQNASFtvQdN0ef0bN+MaNdCWUawpsDLpNWNOyK4FA==} 1635 + workerd@1.20251202.0: 1636 + resolution: {integrity: sha512-p08YfrUMHkjCECNdT36r+6DpJIZX4kixbZ4n6GMUcLR5Gh18fakSCsiQrh72iOm4M9QHv/rM7P8YvCrUPWT5sg==} 1679 1637 engines: {node: '>=16'} 1680 1638 hasBin: true 1681 1639 1682 - wrangler@4.4.0: 1683 - resolution: {integrity: sha512-VmHBpocMk/GTEER+jJzkQeGNx5i/qJJKoUse5zvKmJOnELG/dhEQBJoaWxllwOfaPhIbnqeXdtrN/B+dfQAsFA==} 1684 - engines: {node: '>=18.0.0'} 1640 + wrangler@4.53.0: 1641 + resolution: {integrity: sha512-/wvnHlRnlHsqaeIgGbmcEJE5NFYdTUWHCKow+U5Tv2XwQXI9vXUqBwCLAGy/BwqyS5nnycRt2kppqCzgHgyb7Q==} 1642 + engines: {node: '>=20.0.0'} 1685 1643 hasBin: true 1686 1644 peerDependencies: 1687 - '@cloudflare/workers-types': ^4.20250320.0 1645 + '@cloudflare/workers-types': ^4.20251202.0 1688 1646 peerDependenciesMeta: 1689 1647 '@cloudflare/workers-types': 1690 1648 optional: true 1691 1649 1692 - wrap-ansi@7.0.0: 1693 - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 1694 - engines: {node: '>=10'} 1695 - 1696 - wrap-ansi@8.1.0: 1697 - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 1698 - engines: {node: '>=12'} 1699 - 1700 1650 ws@8.18.0: 1701 1651 resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} 1702 1652 engines: {node: '>=10.0.0'} ··· 1712 1662 yallist@3.1.1: 1713 1663 resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} 1714 1664 1715 - yaml@2.7.0: 1716 - resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} 1717 - engines: {node: '>= 14'} 1718 - hasBin: true 1665 + youch-core@0.3.3: 1666 + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} 1719 1667 1720 - youch@3.2.3: 1721 - resolution: {integrity: sha512-ZBcWz/uzZaQVdCvfV4uk616Bbpf2ee+F/AvuKDR5EwX/Y4v06xWdtMluqTD7+KlZdM93lLm9gMZYo0sKBS0pgw==} 1668 + youch@4.1.0-beta.10: 1669 + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} 1722 1670 1723 1671 zod@3.22.3: 1724 1672 resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} ··· 1727 1675 1728 1676 '@alloc/quick-lru@5.2.0': {} 1729 1677 1730 - '@ampproject/remapping@2.3.0': 1678 + '@atcute/atproto@3.1.9': 1731 1679 dependencies: 1732 - '@jridgewell/gen-mapping': 0.3.8 1733 - '@jridgewell/trace-mapping': 0.3.25 1680 + '@atcute/lexicons': 1.2.5 1734 1681 1735 - '@atcute/bluesky@1.0.14(@atcute/client@2.0.8)': 1682 + '@atcute/bluesky@3.2.13': 1736 1683 dependencies: 1737 - '@atcute/client': 2.0.8 1684 + '@atcute/atproto': 3.1.9 1685 + '@atcute/lexicons': 1.2.5 1738 1686 1739 - '@atcute/car@3.0.0': 1687 + '@atcute/car@5.0.0': 1740 1688 dependencies: 1741 - '@atcute/cbor': 2.2.0 1742 - '@atcute/cid': 2.2.0 1743 - '@atcute/varint': 1.0.2 1689 + '@atcute/cbor': 2.2.8 1690 + '@atcute/cid': 2.2.6 1691 + '@atcute/uint8array': 1.0.6 1692 + '@atcute/varint': 1.0.3 1744 1693 1745 - '@atcute/cbor@2.2.0': 1694 + '@atcute/cbor@2.2.8': 1746 1695 dependencies: 1747 - '@atcute/cid': 2.2.0 1748 - '@atcute/multibase': 1.1.2 1749 - '@atcute/uint8array': 1.0.1 1696 + '@atcute/cid': 2.2.6 1697 + '@atcute/multibase': 1.1.6 1698 + '@atcute/uint8array': 1.0.6 1750 1699 1751 - '@atcute/cid@2.2.0': 1700 + '@atcute/cid@2.2.6': 1752 1701 dependencies: 1753 - '@atcute/multibase': 1.1.2 1754 - '@atcute/uint8array': 1.0.1 1702 + '@atcute/multibase': 1.1.6 1703 + '@atcute/uint8array': 1.0.6 1755 1704 1756 - '@atcute/client@2.0.8': {} 1705 + '@atcute/client@4.1.1': 1706 + dependencies: 1707 + '@atcute/identity': 1.1.3 1708 + '@atcute/lexicons': 1.2.5 1757 1709 1758 - '@atcute/crypto@2.2.0': 1710 + '@atcute/crypto@2.3.0': 1759 1711 dependencies: 1760 - '@atcute/multibase': 1.1.2 1761 - '@atcute/uint8array': 1.0.1 1762 - '@noble/secp256k1': 2.2.3 1712 + '@atcute/multibase': 1.1.6 1713 + '@atcute/uint8array': 1.0.6 1714 + '@noble/secp256k1': 3.0.0 1763 1715 1764 - '@atcute/did-plc@0.1.1': 1716 + '@atcute/did-plc@0.2.0': 1765 1717 dependencies: 1766 - '@atcute/cbor': 2.2.0 1767 - '@atcute/cid': 2.2.0 1768 - '@atcute/crypto': 2.2.0 1769 - '@atcute/multibase': 1.1.2 1770 - '@atcute/uint8array': 1.0.1 1771 - '@badrap/valita': 0.4.3 1718 + '@atcute/cbor': 2.2.8 1719 + '@atcute/cid': 2.2.6 1720 + '@atcute/crypto': 2.3.0 1721 + '@atcute/identity': 1.1.3 1722 + '@atcute/lexicons': 1.2.5 1723 + '@atcute/multibase': 1.1.6 1724 + '@atcute/uint8array': 1.0.6 1725 + '@badrap/valita': 0.4.6 1772 1726 1773 - '@atcute/identity-resolver@0.1.2(@atcute/identity@0.1.1)': 1727 + '@atcute/identity-resolver@1.2.0(@atcute/identity@1.1.3)': 1774 1728 dependencies: 1775 - '@atcute/identity': 0.1.1 1776 - '@atcute/util-fetch': 1.0.1 1777 - '@badrap/valita': 0.4.3 1729 + '@atcute/identity': 1.1.3 1730 + '@atcute/lexicons': 1.2.5 1731 + '@atcute/util-fetch': 1.0.4 1732 + '@badrap/valita': 0.4.6 1778 1733 1779 - '@atcute/identity@0.1.1': 1734 + '@atcute/identity@1.1.3': 1780 1735 dependencies: 1781 - '@badrap/valita': 0.4.3 1736 + '@atcute/lexicons': 1.2.5 1737 + '@badrap/valita': 0.4.6 1782 1738 1783 - '@atcute/multibase@1.1.2': 1739 + '@atcute/lexicons@1.2.5': 1784 1740 dependencies: 1785 - '@atcute/uint8array': 1.0.1 1741 + '@standard-schema/spec': 1.0.0 1742 + esm-env: 1.2.2 1786 1743 1787 - '@atcute/tid@1.0.2': {} 1744 + '@atcute/mst@0.1.0': 1745 + dependencies: 1746 + '@atcute/cbor': 2.2.8 1747 + '@atcute/cid': 2.2.6 1748 + '@atcute/uint8array': 1.0.6 1788 1749 1789 - '@atcute/uint8array@1.0.1': {} 1750 + '@atcute/multibase@1.1.6': 1751 + dependencies: 1752 + '@atcute/uint8array': 1.0.6 1790 1753 1791 - '@atcute/util-fetch@1.0.1': 1754 + '@atcute/repo@0.1.0': 1792 1755 dependencies: 1793 - '@badrap/valita': 0.4.3 1756 + '@atcute/car': 5.0.0 1757 + '@atcute/cbor': 2.2.8 1758 + '@atcute/cid': 2.2.6 1759 + '@atcute/crypto': 2.3.0 1760 + '@atcute/lexicons': 1.2.5 1761 + '@atcute/mst': 0.1.0 1762 + '@atcute/uint8array': 1.0.6 1763 + 1764 + '@atcute/tid@1.0.3': {} 1765 + 1766 + '@atcute/uint8array@1.0.6': {} 1767 + 1768 + '@atcute/util-fetch@1.0.4': 1769 + dependencies: 1770 + '@badrap/valita': 0.4.6 1794 1771 1795 - '@atcute/varint@1.0.2': {} 1772 + '@atcute/varint@1.0.3': {} 1796 1773 1797 - '@babel/code-frame@7.26.2': 1774 + '@babel/code-frame@7.27.1': 1798 1775 dependencies: 1799 - '@babel/helper-validator-identifier': 7.25.9 1776 + '@babel/helper-validator-identifier': 7.28.5 1800 1777 js-tokens: 4.0.0 1801 1778 picocolors: 1.1.1 1802 1779 1803 - '@babel/compat-data@7.26.8': {} 1780 + '@babel/compat-data@7.28.5': {} 1804 1781 1805 - '@babel/core@7.26.10': 1782 + '@babel/core@7.28.5': 1806 1783 dependencies: 1807 - '@ampproject/remapping': 2.3.0 1808 - '@babel/code-frame': 7.26.2 1809 - '@babel/generator': 7.26.10 1810 - '@babel/helper-compilation-targets': 7.26.5 1811 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) 1812 - '@babel/helpers': 7.26.10 1813 - '@babel/parser': 7.26.10 1814 - '@babel/template': 7.26.9 1815 - '@babel/traverse': 7.26.10 1816 - '@babel/types': 7.26.10 1784 + '@babel/code-frame': 7.27.1 1785 + '@babel/generator': 7.28.5 1786 + '@babel/helper-compilation-targets': 7.27.2 1787 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) 1788 + '@babel/helpers': 7.28.4 1789 + '@babel/parser': 7.28.5 1790 + '@babel/template': 7.27.2 1791 + '@babel/traverse': 7.28.5 1792 + '@babel/types': 7.28.5 1793 + '@jridgewell/remapping': 2.3.5 1817 1794 convert-source-map: 2.0.0 1818 - debug: 4.4.0 1795 + debug: 4.4.3 1819 1796 gensync: 1.0.0-beta.2 1820 1797 json5: 2.2.3 1821 1798 semver: 6.3.1 1822 1799 transitivePeerDependencies: 1823 1800 - supports-color 1824 1801 1825 - '@babel/generator@7.26.10': 1802 + '@babel/generator@7.28.5': 1826 1803 dependencies: 1827 - '@babel/parser': 7.26.10 1828 - '@babel/types': 7.26.10 1829 - '@jridgewell/gen-mapping': 0.3.8 1830 - '@jridgewell/trace-mapping': 0.3.25 1804 + '@babel/parser': 7.28.5 1805 + '@babel/types': 7.28.5 1806 + '@jridgewell/gen-mapping': 0.3.13 1807 + '@jridgewell/trace-mapping': 0.3.31 1831 1808 jsesc: 3.1.0 1832 1809 1833 - '@babel/helper-compilation-targets@7.26.5': 1810 + '@babel/helper-compilation-targets@7.27.2': 1834 1811 dependencies: 1835 - '@babel/compat-data': 7.26.8 1836 - '@babel/helper-validator-option': 7.25.9 1837 - browserslist: 4.24.4 1812 + '@babel/compat-data': 7.28.5 1813 + '@babel/helper-validator-option': 7.27.1 1814 + browserslist: 4.28.1 1838 1815 lru-cache: 5.1.1 1839 1816 semver: 6.3.1 1840 1817 1818 + '@babel/helper-globals@7.28.0': {} 1819 + 1841 1820 '@babel/helper-module-imports@7.18.6': 1842 1821 dependencies: 1843 - '@babel/types': 7.26.10 1822 + '@babel/types': 7.28.5 1844 1823 1845 - '@babel/helper-module-imports@7.25.9': 1824 + '@babel/helper-module-imports@7.27.1': 1846 1825 dependencies: 1847 - '@babel/traverse': 7.26.10 1848 - '@babel/types': 7.26.10 1826 + '@babel/traverse': 7.28.5 1827 + '@babel/types': 7.28.5 1849 1828 transitivePeerDependencies: 1850 1829 - supports-color 1851 1830 1852 - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': 1831 + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': 1853 1832 dependencies: 1854 - '@babel/core': 7.26.10 1855 - '@babel/helper-module-imports': 7.25.9 1856 - '@babel/helper-validator-identifier': 7.25.9 1857 - '@babel/traverse': 7.26.10 1833 + '@babel/core': 7.28.5 1834 + '@babel/helper-module-imports': 7.27.1 1835 + '@babel/helper-validator-identifier': 7.28.5 1836 + '@babel/traverse': 7.28.5 1858 1837 transitivePeerDependencies: 1859 1838 - supports-color 1860 1839 1861 - '@babel/helper-plugin-utils@7.26.5': {} 1840 + '@babel/helper-plugin-utils@7.27.1': {} 1862 1841 1863 - '@babel/helper-string-parser@7.25.9': {} 1842 + '@babel/helper-string-parser@7.27.1': {} 1864 1843 1865 - '@babel/helper-validator-identifier@7.25.9': {} 1844 + '@babel/helper-validator-identifier@7.28.5': {} 1866 1845 1867 - '@babel/helper-validator-option@7.25.9': {} 1846 + '@babel/helper-validator-option@7.27.1': {} 1868 1847 1869 - '@babel/helpers@7.26.10': 1848 + '@babel/helpers@7.28.4': 1870 1849 dependencies: 1871 - '@babel/template': 7.26.9 1872 - '@babel/types': 7.26.10 1850 + '@babel/template': 7.27.2 1851 + '@babel/types': 7.28.5 1873 1852 1874 - '@babel/parser@7.26.10': 1853 + '@babel/parser@7.28.5': 1875 1854 dependencies: 1876 - '@babel/types': 7.26.10 1855 + '@babel/types': 7.28.5 1877 1856 1878 - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10)': 1857 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': 1879 1858 dependencies: 1880 - '@babel/core': 7.26.10 1881 - '@babel/helper-plugin-utils': 7.26.5 1859 + '@babel/core': 7.28.5 1860 + '@babel/helper-plugin-utils': 7.27.1 1882 1861 1883 - '@babel/template@7.26.9': 1862 + '@babel/template@7.27.2': 1884 1863 dependencies: 1885 - '@babel/code-frame': 7.26.2 1886 - '@babel/parser': 7.26.10 1887 - '@babel/types': 7.26.10 1864 + '@babel/code-frame': 7.27.1 1865 + '@babel/parser': 7.28.5 1866 + '@babel/types': 7.28.5 1888 1867 1889 - '@babel/traverse@7.26.10': 1868 + '@babel/traverse@7.28.5': 1890 1869 dependencies: 1891 - '@babel/code-frame': 7.26.2 1892 - '@babel/generator': 7.26.10 1893 - '@babel/parser': 7.26.10 1894 - '@babel/template': 7.26.9 1895 - '@babel/types': 7.26.10 1896 - debug: 4.4.0 1897 - globals: 11.12.0 1870 + '@babel/code-frame': 7.27.1 1871 + '@babel/generator': 7.28.5 1872 + '@babel/helper-globals': 7.28.0 1873 + '@babel/parser': 7.28.5 1874 + '@babel/template': 7.27.2 1875 + '@babel/types': 7.28.5 1876 + debug: 4.4.3 1898 1877 transitivePeerDependencies: 1899 1878 - supports-color 1900 1879 1901 - '@babel/types@7.26.10': 1880 + '@babel/types@7.28.5': 1902 1881 dependencies: 1903 - '@babel/helper-string-parser': 7.25.9 1904 - '@babel/helper-validator-identifier': 7.25.9 1882 + '@babel/helper-string-parser': 7.27.1 1883 + '@babel/helper-validator-identifier': 7.28.5 1905 1884 1906 - '@badrap/valita@0.4.3': {} 1885 + '@badrap/valita@0.4.6': {} 1907 1886 1908 - '@cloudflare/kv-asset-handler@0.4.0': 1887 + '@cloudflare/kv-asset-handler@0.4.1': 1909 1888 dependencies: 1910 1889 mime: 3.0.0 1911 1890 1912 - '@cloudflare/unenv-preset@2.3.0(unenv@2.0.0-rc.15)(workerd@1.20250320.0)': 1891 + '@cloudflare/unenv-preset@2.7.13(unenv@2.0.0-rc.24)(workerd@1.20251202.0)': 1913 1892 dependencies: 1914 - unenv: 2.0.0-rc.15 1893 + unenv: 2.0.0-rc.24 1915 1894 optionalDependencies: 1916 - workerd: 1.20250320.0 1895 + workerd: 1.20251202.0 1917 1896 1918 - '@cloudflare/workerd-darwin-64@1.20250320.0': 1897 + '@cloudflare/workerd-darwin-64@1.20251202.0': 1919 1898 optional: true 1920 1899 1921 - '@cloudflare/workerd-darwin-arm64@1.20250320.0': 1900 + '@cloudflare/workerd-darwin-arm64@1.20251202.0': 1922 1901 optional: true 1923 1902 1924 - '@cloudflare/workerd-linux-64@1.20250320.0': 1903 + '@cloudflare/workerd-linux-64@1.20251202.0': 1925 1904 optional: true 1926 1905 1927 - '@cloudflare/workerd-linux-arm64@1.20250320.0': 1906 + '@cloudflare/workerd-linux-arm64@1.20251202.0': 1928 1907 optional: true 1929 1908 1930 - '@cloudflare/workerd-windows-64@1.20250320.0': 1909 + '@cloudflare/workerd-windows-64@1.20251202.0': 1931 1910 optional: true 1932 1911 1933 1912 '@cspotcode/source-map-support@0.8.1': 1934 1913 dependencies: 1935 1914 '@jridgewell/trace-mapping': 0.3.9 1936 1915 1937 - '@emnapi/runtime@1.3.1': 1916 + '@emnapi/runtime@1.7.1': 1938 1917 dependencies: 1939 1918 tslib: 2.8.1 1940 1919 optional: true 1941 1920 1942 - '@esbuild/aix-ppc64@0.24.2': 1921 + '@esbuild/aix-ppc64@0.25.12': 1922 + optional: true 1923 + 1924 + '@esbuild/aix-ppc64@0.27.0': 1943 1925 optional: true 1944 1926 1945 - '@esbuild/aix-ppc64@0.25.1': 1927 + '@esbuild/android-arm64@0.25.12': 1928 + optional: true 1929 + 1930 + '@esbuild/android-arm64@0.27.0': 1946 1931 optional: true 1947 1932 1948 - '@esbuild/android-arm64@0.24.2': 1933 + '@esbuild/android-arm@0.25.12': 1949 1934 optional: true 1950 1935 1951 - '@esbuild/android-arm64@0.25.1': 1936 + '@esbuild/android-arm@0.27.0': 1952 1937 optional: true 1953 1938 1954 - '@esbuild/android-arm@0.24.2': 1939 + '@esbuild/android-x64@0.25.12': 1955 1940 optional: true 1956 1941 1957 - '@esbuild/android-arm@0.25.1': 1942 + '@esbuild/android-x64@0.27.0': 1958 1943 optional: true 1959 1944 1960 - '@esbuild/android-x64@0.24.2': 1945 + '@esbuild/darwin-arm64@0.25.12': 1961 1946 optional: true 1962 1947 1963 - '@esbuild/android-x64@0.25.1': 1948 + '@esbuild/darwin-arm64@0.27.0': 1964 1949 optional: true 1965 1950 1966 - '@esbuild/darwin-arm64@0.24.2': 1951 + '@esbuild/darwin-x64@0.25.12': 1967 1952 optional: true 1968 1953 1969 - '@esbuild/darwin-arm64@0.25.1': 1954 + '@esbuild/darwin-x64@0.27.0': 1970 1955 optional: true 1971 1956 1972 - '@esbuild/darwin-x64@0.24.2': 1957 + '@esbuild/freebsd-arm64@0.25.12': 1973 1958 optional: true 1974 1959 1975 - '@esbuild/darwin-x64@0.25.1': 1960 + '@esbuild/freebsd-arm64@0.27.0': 1976 1961 optional: true 1977 1962 1978 - '@esbuild/freebsd-arm64@0.24.2': 1963 + '@esbuild/freebsd-x64@0.25.12': 1979 1964 optional: true 1980 1965 1981 - '@esbuild/freebsd-arm64@0.25.1': 1966 + '@esbuild/freebsd-x64@0.27.0': 1982 1967 optional: true 1983 1968 1984 - '@esbuild/freebsd-x64@0.24.2': 1969 + '@esbuild/linux-arm64@0.25.12': 1985 1970 optional: true 1986 1971 1987 - '@esbuild/freebsd-x64@0.25.1': 1972 + '@esbuild/linux-arm64@0.27.0': 1988 1973 optional: true 1989 1974 1990 - '@esbuild/linux-arm64@0.24.2': 1975 + '@esbuild/linux-arm@0.25.12': 1991 1976 optional: true 1992 1977 1993 - '@esbuild/linux-arm64@0.25.1': 1978 + '@esbuild/linux-arm@0.27.0': 1994 1979 optional: true 1995 1980 1996 - '@esbuild/linux-arm@0.24.2': 1981 + '@esbuild/linux-ia32@0.25.12': 1997 1982 optional: true 1998 1983 1999 - '@esbuild/linux-arm@0.25.1': 1984 + '@esbuild/linux-ia32@0.27.0': 2000 1985 optional: true 2001 1986 2002 - '@esbuild/linux-ia32@0.24.2': 1987 + '@esbuild/linux-loong64@0.25.12': 2003 1988 optional: true 2004 1989 2005 - '@esbuild/linux-ia32@0.25.1': 1990 + '@esbuild/linux-loong64@0.27.0': 2006 1991 optional: true 2007 1992 2008 - '@esbuild/linux-loong64@0.24.2': 1993 + '@esbuild/linux-mips64el@0.25.12': 2009 1994 optional: true 2010 1995 2011 - '@esbuild/linux-loong64@0.25.1': 1996 + '@esbuild/linux-mips64el@0.27.0': 2012 1997 optional: true 2013 1998 2014 - '@esbuild/linux-mips64el@0.24.2': 1999 + '@esbuild/linux-ppc64@0.25.12': 2015 2000 optional: true 2016 2001 2017 - '@esbuild/linux-mips64el@0.25.1': 2002 + '@esbuild/linux-ppc64@0.27.0': 2018 2003 optional: true 2019 2004 2020 - '@esbuild/linux-ppc64@0.24.2': 2005 + '@esbuild/linux-riscv64@0.25.12': 2021 2006 optional: true 2022 2007 2023 - '@esbuild/linux-ppc64@0.25.1': 2008 + '@esbuild/linux-riscv64@0.27.0': 2024 2009 optional: true 2025 2010 2026 - '@esbuild/linux-riscv64@0.24.2': 2011 + '@esbuild/linux-s390x@0.25.12': 2027 2012 optional: true 2028 2013 2029 - '@esbuild/linux-riscv64@0.25.1': 2014 + '@esbuild/linux-s390x@0.27.0': 2030 2015 optional: true 2031 2016 2032 - '@esbuild/linux-s390x@0.24.2': 2017 + '@esbuild/linux-x64@0.25.12': 2033 2018 optional: true 2034 2019 2035 - '@esbuild/linux-s390x@0.25.1': 2020 + '@esbuild/linux-x64@0.27.0': 2036 2021 optional: true 2037 2022 2038 - '@esbuild/linux-x64@0.24.2': 2023 + '@esbuild/netbsd-arm64@0.25.12': 2039 2024 optional: true 2040 2025 2041 - '@esbuild/linux-x64@0.25.1': 2026 + '@esbuild/netbsd-arm64@0.27.0': 2042 2027 optional: true 2043 2028 2044 - '@esbuild/netbsd-arm64@0.24.2': 2029 + '@esbuild/netbsd-x64@0.25.12': 2045 2030 optional: true 2046 2031 2047 - '@esbuild/netbsd-arm64@0.25.1': 2032 + '@esbuild/netbsd-x64@0.27.0': 2048 2033 optional: true 2049 2034 2050 - '@esbuild/netbsd-x64@0.24.2': 2035 + '@esbuild/openbsd-arm64@0.25.12': 2051 2036 optional: true 2052 2037 2053 - '@esbuild/netbsd-x64@0.25.1': 2038 + '@esbuild/openbsd-arm64@0.27.0': 2054 2039 optional: true 2055 2040 2056 - '@esbuild/openbsd-arm64@0.24.2': 2041 + '@esbuild/openbsd-x64@0.25.12': 2057 2042 optional: true 2058 2043 2059 - '@esbuild/openbsd-arm64@0.25.1': 2044 + '@esbuild/openbsd-x64@0.27.0': 2060 2045 optional: true 2061 2046 2062 - '@esbuild/openbsd-x64@0.24.2': 2047 + '@esbuild/openharmony-arm64@0.25.12': 2063 2048 optional: true 2064 2049 2065 - '@esbuild/openbsd-x64@0.25.1': 2050 + '@esbuild/openharmony-arm64@0.27.0': 2066 2051 optional: true 2067 2052 2068 - '@esbuild/sunos-x64@0.24.2': 2053 + '@esbuild/sunos-x64@0.25.12': 2069 2054 optional: true 2070 2055 2071 - '@esbuild/sunos-x64@0.25.1': 2056 + '@esbuild/sunos-x64@0.27.0': 2072 2057 optional: true 2073 2058 2074 - '@esbuild/win32-arm64@0.24.2': 2059 + '@esbuild/win32-arm64@0.25.12': 2075 2060 optional: true 2076 2061 2077 - '@esbuild/win32-arm64@0.25.1': 2062 + '@esbuild/win32-arm64@0.27.0': 2078 2063 optional: true 2079 2064 2080 - '@esbuild/win32-ia32@0.24.2': 2065 + '@esbuild/win32-ia32@0.25.12': 2081 2066 optional: true 2082 2067 2083 - '@esbuild/win32-ia32@0.25.1': 2068 + '@esbuild/win32-ia32@0.27.0': 2084 2069 optional: true 2085 2070 2086 - '@esbuild/win32-x64@0.24.2': 2071 + '@esbuild/win32-x64@0.25.12': 2087 2072 optional: true 2088 2073 2089 - '@esbuild/win32-x64@0.25.1': 2074 + '@esbuild/win32-x64@0.27.0': 2090 2075 optional: true 2091 2076 2092 - '@externdefs/solid-freeze@0.1.1(solid-js@1.9.5)': 2077 + '@externdefs/solid-freeze@0.1.1(solid-js@1.9.10)': 2093 2078 dependencies: 2094 - solid-js: 1.9.5 2095 - 2096 - '@fastify/busboy@2.1.1': {} 2079 + solid-js: 1.9.10 2097 2080 2098 2081 '@img/sharp-darwin-arm64@0.33.5': 2099 2082 optionalDependencies: ··· 2161 2144 2162 2145 '@img/sharp-wasm32@0.33.5': 2163 2146 dependencies: 2164 - '@emnapi/runtime': 1.3.1 2147 + '@emnapi/runtime': 1.7.1 2165 2148 optional: true 2166 2149 2167 2150 '@img/sharp-win32-ia32@0.33.5': ··· 2170 2153 '@img/sharp-win32-x64@0.33.5': 2171 2154 optional: true 2172 2155 2173 - '@isaacs/cliui@8.0.2': 2156 + '@jridgewell/gen-mapping@0.3.13': 2174 2157 dependencies: 2175 - string-width: 5.1.2 2176 - string-width-cjs: string-width@4.2.3 2177 - strip-ansi: 7.1.0 2178 - strip-ansi-cjs: strip-ansi@6.0.1 2179 - wrap-ansi: 8.1.0 2180 - wrap-ansi-cjs: wrap-ansi@7.0.0 2158 + '@jridgewell/sourcemap-codec': 1.5.5 2159 + '@jridgewell/trace-mapping': 0.3.31 2181 2160 2182 - '@jridgewell/gen-mapping@0.3.8': 2161 + '@jridgewell/remapping@2.3.5': 2183 2162 dependencies: 2184 - '@jridgewell/set-array': 1.2.1 2185 - '@jridgewell/sourcemap-codec': 1.5.0 2186 - '@jridgewell/trace-mapping': 0.3.25 2163 + '@jridgewell/gen-mapping': 0.3.13 2164 + '@jridgewell/trace-mapping': 0.3.31 2187 2165 2188 2166 '@jridgewell/resolve-uri@3.1.2': {} 2189 2167 2190 - '@jridgewell/set-array@1.2.1': {} 2191 - 2192 - '@jridgewell/source-map@0.3.6': 2168 + '@jridgewell/source-map@0.3.11': 2193 2169 dependencies: 2194 - '@jridgewell/gen-mapping': 0.3.8 2195 - '@jridgewell/trace-mapping': 0.3.25 2170 + '@jridgewell/gen-mapping': 0.3.13 2171 + '@jridgewell/trace-mapping': 0.3.31 2196 2172 2197 - '@jridgewell/sourcemap-codec@1.5.0': {} 2173 + '@jridgewell/sourcemap-codec@1.5.5': {} 2198 2174 2199 - '@jridgewell/trace-mapping@0.3.25': 2175 + '@jridgewell/trace-mapping@0.3.31': 2200 2176 dependencies: 2201 2177 '@jridgewell/resolve-uri': 3.1.2 2202 - '@jridgewell/sourcemap-codec': 1.5.0 2178 + '@jridgewell/sourcemap-codec': 1.5.5 2203 2179 2204 2180 '@jridgewell/trace-mapping@0.3.9': 2205 2181 dependencies: 2206 2182 '@jridgewell/resolve-uri': 3.1.2 2207 - '@jridgewell/sourcemap-codec': 1.5.0 2183 + '@jridgewell/sourcemap-codec': 1.5.5 2208 2184 2209 - '@jsr/mary__array-fns@0.1.4': {} 2185 + '@jsr/mary__array-fns@0.1.5': {} 2186 + 2187 + '@jsr/mary__ds-queue@0.1.3': {} 2210 2188 2211 - '@jsr/mary__events@0.1.0': {} 2189 + '@jsr/mary__events@0.2.0': {} 2212 2190 2213 - '@jsr/mary__tar@0.2.4': {} 2191 + '@jsr/mary__tar@0.3.1': {} 2214 2192 2215 - '@noble/secp256k1@2.2.3': {} 2193 + '@noble/secp256k1@3.0.0': {} 2216 2194 2217 2195 '@nodelib/fs.scandir@2.1.5': 2218 2196 dependencies: ··· 2226 2204 '@nodelib/fs.scandir': 2.1.5 2227 2205 fastq: 1.19.1 2228 2206 2229 - '@pkgjs/parseargs@0.11.0': 2207 + '@poppinss/colors@4.1.5': 2208 + dependencies: 2209 + kleur: 4.1.5 2210 + 2211 + '@poppinss/dumper@0.6.5': 2212 + dependencies: 2213 + '@poppinss/colors': 4.1.5 2214 + '@sindresorhus/is': 7.1.1 2215 + supports-color: 10.2.2 2216 + 2217 + '@poppinss/exception@1.2.2': {} 2218 + 2219 + '@rollup/rollup-android-arm-eabi@4.53.3': 2230 2220 optional: true 2231 2221 2232 - '@rollup/rollup-android-arm-eabi@4.37.0': 2222 + '@rollup/rollup-android-arm64@4.53.3': 2233 2223 optional: true 2234 2224 2235 - '@rollup/rollup-android-arm64@4.37.0': 2225 + '@rollup/rollup-darwin-arm64@4.53.3': 2236 2226 optional: true 2237 2227 2238 - '@rollup/rollup-darwin-arm64@4.37.0': 2228 + '@rollup/rollup-darwin-x64@4.53.3': 2239 2229 optional: true 2240 2230 2241 - '@rollup/rollup-darwin-x64@4.37.0': 2231 + '@rollup/rollup-freebsd-arm64@4.53.3': 2242 2232 optional: true 2243 2233 2244 - '@rollup/rollup-freebsd-arm64@4.37.0': 2234 + '@rollup/rollup-freebsd-x64@4.53.3': 2245 2235 optional: true 2246 2236 2247 - '@rollup/rollup-freebsd-x64@4.37.0': 2237 + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': 2248 2238 optional: true 2249 2239 2250 - '@rollup/rollup-linux-arm-gnueabihf@4.37.0': 2240 + '@rollup/rollup-linux-arm-musleabihf@4.53.3': 2251 2241 optional: true 2252 2242 2253 - '@rollup/rollup-linux-arm-musleabihf@4.37.0': 2243 + '@rollup/rollup-linux-arm64-gnu@4.53.3': 2254 2244 optional: true 2255 2245 2256 - '@rollup/rollup-linux-arm64-gnu@4.37.0': 2246 + '@rollup/rollup-linux-arm64-musl@4.53.3': 2257 2247 optional: true 2258 2248 2259 - '@rollup/rollup-linux-arm64-musl@4.37.0': 2249 + '@rollup/rollup-linux-loong64-gnu@4.53.3': 2260 2250 optional: true 2261 2251 2262 - '@rollup/rollup-linux-loongarch64-gnu@4.37.0': 2252 + '@rollup/rollup-linux-ppc64-gnu@4.53.3': 2263 2253 optional: true 2264 2254 2265 - '@rollup/rollup-linux-powerpc64le-gnu@4.37.0': 2255 + '@rollup/rollup-linux-riscv64-gnu@4.53.3': 2256 + optional: true 2257 + 2258 + '@rollup/rollup-linux-riscv64-musl@4.53.3': 2266 2259 optional: true 2267 2260 2268 - '@rollup/rollup-linux-riscv64-gnu@4.37.0': 2261 + '@rollup/rollup-linux-s390x-gnu@4.53.3': 2269 2262 optional: true 2270 2263 2271 - '@rollup/rollup-linux-riscv64-musl@4.37.0': 2264 + '@rollup/rollup-linux-x64-gnu@4.53.3': 2272 2265 optional: true 2273 2266 2274 - '@rollup/rollup-linux-s390x-gnu@4.37.0': 2267 + '@rollup/rollup-linux-x64-musl@4.53.3': 2275 2268 optional: true 2276 2269 2277 - '@rollup/rollup-linux-x64-gnu@4.37.0': 2270 + '@rollup/rollup-openharmony-arm64@4.53.3': 2278 2271 optional: true 2279 2272 2280 - '@rollup/rollup-linux-x64-musl@4.37.0': 2273 + '@rollup/rollup-win32-arm64-msvc@4.53.3': 2281 2274 optional: true 2282 2275 2283 - '@rollup/rollup-win32-arm64-msvc@4.37.0': 2276 + '@rollup/rollup-win32-ia32-msvc@4.53.3': 2284 2277 optional: true 2285 2278 2286 - '@rollup/rollup-win32-ia32-msvc@4.37.0': 2279 + '@rollup/rollup-win32-x64-gnu@4.53.3': 2287 2280 optional: true 2288 2281 2289 - '@rollup/rollup-win32-x64-msvc@4.37.0': 2282 + '@rollup/rollup-win32-x64-msvc@4.53.3': 2290 2283 optional: true 2291 2284 2292 - '@tailwindcss/forms@0.5.10(tailwindcss@3.4.17)': 2285 + '@sindresorhus/is@7.1.1': {} 2286 + 2287 + '@speed-highlight/core@1.2.12': {} 2288 + 2289 + '@standard-schema/spec@1.0.0': {} 2290 + 2291 + '@tailwindcss/forms@0.5.10(tailwindcss@3.4.18)': 2293 2292 dependencies: 2294 2293 mini-svg-data-uri: 1.4.4 2295 - tailwindcss: 3.4.17 2294 + tailwindcss: 3.4.18 2296 2295 2297 2296 '@types/babel__core@7.20.5': 2298 2297 dependencies: 2299 - '@babel/parser': 7.26.10 2300 - '@babel/types': 7.26.10 2301 - '@types/babel__generator': 7.6.8 2298 + '@babel/parser': 7.28.5 2299 + '@babel/types': 7.28.5 2300 + '@types/babel__generator': 7.27.0 2302 2301 '@types/babel__template': 7.4.4 2303 - '@types/babel__traverse': 7.20.6 2302 + '@types/babel__traverse': 7.28.0 2304 2303 2305 - '@types/babel__generator@7.6.8': 2304 + '@types/babel__generator@7.27.0': 2306 2305 dependencies: 2307 - '@babel/types': 7.26.10 2306 + '@babel/types': 7.28.5 2308 2307 2309 2308 '@types/babel__template@7.4.4': 2310 2309 dependencies: 2311 - '@babel/parser': 7.26.10 2312 - '@babel/types': 7.26.10 2310 + '@babel/parser': 7.28.5 2311 + '@babel/types': 7.28.5 2313 2312 2314 - '@types/babel__traverse@7.20.6': 2313 + '@types/babel__traverse@7.28.0': 2315 2314 dependencies: 2316 - '@babel/types': 7.26.10 2315 + '@babel/types': 7.28.5 2317 2316 2318 - '@types/estree@1.0.6': {} 2317 + '@types/estree@1.0.8': {} 2319 2318 2320 - '@types/node@22.13.12': 2319 + '@types/node@22.19.2': 2321 2320 dependencies: 2322 - undici-types: 6.20.0 2321 + undici-types: 6.21.0 2323 2322 2324 2323 acorn-walk@8.3.2: {} 2325 2324 2326 2325 acorn@8.14.0: {} 2327 2326 2328 - acorn@8.14.1: {} 2329 - 2330 - ansi-regex@5.0.1: {} 2331 - 2332 - ansi-regex@6.1.0: {} 2333 - 2334 - ansi-styles@4.3.0: 2335 - dependencies: 2336 - color-convert: 2.0.1 2337 - 2338 - ansi-styles@6.2.1: {} 2327 + acorn@8.15.0: {} 2339 2328 2340 2329 any-promise@1.3.0: {} 2341 2330 ··· 2346 2335 2347 2336 arg@5.0.2: {} 2348 2337 2349 - as-table@1.0.55: 2350 - dependencies: 2351 - printable-characters: 1.0.42 2352 - 2353 - autoprefixer@10.4.21(postcss@8.5.3): 2338 + autoprefixer@10.4.22(postcss@8.5.6): 2354 2339 dependencies: 2355 - browserslist: 4.24.4 2356 - caniuse-lite: 1.0.30001707 2357 - fraction.js: 4.3.7 2340 + browserslist: 4.28.1 2341 + caniuse-lite: 1.0.30001760 2342 + fraction.js: 5.3.4 2358 2343 normalize-range: 0.1.2 2359 2344 picocolors: 1.1.1 2360 - postcss: 8.5.3 2345 + postcss: 8.5.6 2361 2346 postcss-value-parser: 4.2.0 2362 2347 2363 - babel-plugin-jsx-dom-expressions@0.39.7(@babel/core@7.26.10): 2348 + babel-plugin-jsx-dom-expressions@0.40.3(@babel/core@7.28.5): 2364 2349 dependencies: 2365 - '@babel/core': 7.26.10 2350 + '@babel/core': 7.28.5 2366 2351 '@babel/helper-module-imports': 7.18.6 2367 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) 2368 - '@babel/types': 7.26.10 2352 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) 2353 + '@babel/types': 7.28.5 2369 2354 html-entities: 2.3.3 2370 - parse5: 7.2.1 2371 - validate-html-nesting: 1.2.2 2355 + parse5: 7.3.0 2372 2356 2373 - babel-preset-solid@1.9.5(@babel/core@7.26.10): 2357 + babel-preset-solid@1.9.10(@babel/core@7.28.5)(solid-js@1.9.10): 2374 2358 dependencies: 2375 - '@babel/core': 7.26.10 2376 - babel-plugin-jsx-dom-expressions: 0.39.7(@babel/core@7.26.10) 2359 + '@babel/core': 7.28.5 2360 + babel-plugin-jsx-dom-expressions: 0.40.3(@babel/core@7.28.5) 2361 + optionalDependencies: 2362 + solid-js: 1.9.10 2377 2363 2378 - balanced-match@1.0.2: {} 2364 + baseline-browser-mapping@2.9.5: {} 2379 2365 2380 2366 binary-extensions@2.3.0: {} 2381 2367 2382 2368 blake3-wasm@2.1.5: {} 2383 - 2384 - brace-expansion@2.0.1: 2385 - dependencies: 2386 - balanced-match: 1.0.2 2387 2369 2388 2370 braces@3.0.3: 2389 2371 dependencies: 2390 2372 fill-range: 7.1.1 2391 2373 2392 - browserslist@4.24.4: 2374 + browserslist@4.28.1: 2393 2375 dependencies: 2394 - caniuse-lite: 1.0.30001707 2395 - electron-to-chromium: 1.5.123 2396 - node-releases: 2.0.19 2397 - update-browserslist-db: 1.1.3(browserslist@4.24.4) 2376 + baseline-browser-mapping: 2.9.5 2377 + caniuse-lite: 1.0.30001760 2378 + electron-to-chromium: 1.5.267 2379 + node-releases: 2.0.27 2380 + update-browserslist-db: 1.2.2(browserslist@4.28.1) 2398 2381 2399 2382 buffer-from@1.1.2: {} 2400 2383 2401 2384 camelcase-css@2.0.1: {} 2402 2385 2403 - caniuse-lite@1.0.30001707: {} 2386 + caniuse-lite@1.0.30001760: {} 2404 2387 2405 2388 chokidar@3.6.0: 2406 2389 dependencies: ··· 2423 2406 color-string@1.9.1: 2424 2407 dependencies: 2425 2408 color-name: 1.1.4 2426 - simple-swizzle: 0.2.2 2427 - optional: true 2409 + simple-swizzle: 0.2.4 2428 2410 2429 2411 color@4.2.3: 2430 2412 dependencies: 2431 2413 color-convert: 2.0.1 2432 2414 color-string: 1.9.1 2433 - optional: true 2434 2415 2435 2416 commander@2.20.3: {} 2436 2417 ··· 2438 2419 2439 2420 convert-source-map@2.0.0: {} 2440 2421 2441 - cookie@0.5.0: {} 2442 - 2443 - cross-spawn@7.0.6: 2444 - dependencies: 2445 - path-key: 3.1.1 2446 - shebang-command: 2.0.0 2447 - which: 2.0.2 2422 + cookie@1.1.1: {} 2448 2423 2449 2424 cssesc@3.0.0: {} 2450 2425 2451 - csstype@3.1.3: {} 2426 + csstype@3.2.3: {} 2452 2427 2453 - data-uri-to-buffer@2.0.2: {} 2454 - 2455 - debug@4.4.0: 2428 + debug@4.4.3: 2456 2429 dependencies: 2457 2430 ms: 2.1.3 2458 2431 2459 - defu@6.1.4: {} 2460 - 2461 - detect-libc@2.0.3: 2462 - optional: true 2432 + detect-libc@2.1.2: {} 2463 2433 2464 2434 didyoumean@1.2.2: {} 2465 2435 2466 2436 dlv@1.1.3: {} 2467 2437 2468 - eastasianwidth@0.2.0: {} 2438 + electron-to-chromium@1.5.267: {} 2469 2439 2470 - electron-to-chromium@1.5.123: {} 2440 + entities@6.0.1: {} 2471 2441 2472 - emoji-regex@8.0.0: {} 2442 + error-stack-parser-es@1.0.5: {} 2473 2443 2474 - emoji-regex@9.2.2: {} 2475 - 2476 - entities@4.5.0: {} 2477 - 2478 - esbuild@0.24.2: 2444 + esbuild@0.25.12: 2479 2445 optionalDependencies: 2480 - '@esbuild/aix-ppc64': 0.24.2 2481 - '@esbuild/android-arm': 0.24.2 2482 - '@esbuild/android-arm64': 0.24.2 2483 - '@esbuild/android-x64': 0.24.2 2484 - '@esbuild/darwin-arm64': 0.24.2 2485 - '@esbuild/darwin-x64': 0.24.2 2486 - '@esbuild/freebsd-arm64': 0.24.2 2487 - '@esbuild/freebsd-x64': 0.24.2 2488 - '@esbuild/linux-arm': 0.24.2 2489 - '@esbuild/linux-arm64': 0.24.2 2490 - '@esbuild/linux-ia32': 0.24.2 2491 - '@esbuild/linux-loong64': 0.24.2 2492 - '@esbuild/linux-mips64el': 0.24.2 2493 - '@esbuild/linux-ppc64': 0.24.2 2494 - '@esbuild/linux-riscv64': 0.24.2 2495 - '@esbuild/linux-s390x': 0.24.2 2496 - '@esbuild/linux-x64': 0.24.2 2497 - '@esbuild/netbsd-arm64': 0.24.2 2498 - '@esbuild/netbsd-x64': 0.24.2 2499 - '@esbuild/openbsd-arm64': 0.24.2 2500 - '@esbuild/openbsd-x64': 0.24.2 2501 - '@esbuild/sunos-x64': 0.24.2 2502 - '@esbuild/win32-arm64': 0.24.2 2503 - '@esbuild/win32-ia32': 0.24.2 2504 - '@esbuild/win32-x64': 0.24.2 2446 + '@esbuild/aix-ppc64': 0.25.12 2447 + '@esbuild/android-arm': 0.25.12 2448 + '@esbuild/android-arm64': 0.25.12 2449 + '@esbuild/android-x64': 0.25.12 2450 + '@esbuild/darwin-arm64': 0.25.12 2451 + '@esbuild/darwin-x64': 0.25.12 2452 + '@esbuild/freebsd-arm64': 0.25.12 2453 + '@esbuild/freebsd-x64': 0.25.12 2454 + '@esbuild/linux-arm': 0.25.12 2455 + '@esbuild/linux-arm64': 0.25.12 2456 + '@esbuild/linux-ia32': 0.25.12 2457 + '@esbuild/linux-loong64': 0.25.12 2458 + '@esbuild/linux-mips64el': 0.25.12 2459 + '@esbuild/linux-ppc64': 0.25.12 2460 + '@esbuild/linux-riscv64': 0.25.12 2461 + '@esbuild/linux-s390x': 0.25.12 2462 + '@esbuild/linux-x64': 0.25.12 2463 + '@esbuild/netbsd-arm64': 0.25.12 2464 + '@esbuild/netbsd-x64': 0.25.12 2465 + '@esbuild/openbsd-arm64': 0.25.12 2466 + '@esbuild/openbsd-x64': 0.25.12 2467 + '@esbuild/openharmony-arm64': 0.25.12 2468 + '@esbuild/sunos-x64': 0.25.12 2469 + '@esbuild/win32-arm64': 0.25.12 2470 + '@esbuild/win32-ia32': 0.25.12 2471 + '@esbuild/win32-x64': 0.25.12 2505 2472 2506 - esbuild@0.25.1: 2473 + esbuild@0.27.0: 2507 2474 optionalDependencies: 2508 - '@esbuild/aix-ppc64': 0.25.1 2509 - '@esbuild/android-arm': 0.25.1 2510 - '@esbuild/android-arm64': 0.25.1 2511 - '@esbuild/android-x64': 0.25.1 2512 - '@esbuild/darwin-arm64': 0.25.1 2513 - '@esbuild/darwin-x64': 0.25.1 2514 - '@esbuild/freebsd-arm64': 0.25.1 2515 - '@esbuild/freebsd-x64': 0.25.1 2516 - '@esbuild/linux-arm': 0.25.1 2517 - '@esbuild/linux-arm64': 0.25.1 2518 - '@esbuild/linux-ia32': 0.25.1 2519 - '@esbuild/linux-loong64': 0.25.1 2520 - '@esbuild/linux-mips64el': 0.25.1 2521 - '@esbuild/linux-ppc64': 0.25.1 2522 - '@esbuild/linux-riscv64': 0.25.1 2523 - '@esbuild/linux-s390x': 0.25.1 2524 - '@esbuild/linux-x64': 0.25.1 2525 - '@esbuild/netbsd-arm64': 0.25.1 2526 - '@esbuild/netbsd-x64': 0.25.1 2527 - '@esbuild/openbsd-arm64': 0.25.1 2528 - '@esbuild/openbsd-x64': 0.25.1 2529 - '@esbuild/sunos-x64': 0.25.1 2530 - '@esbuild/win32-arm64': 0.25.1 2531 - '@esbuild/win32-ia32': 0.25.1 2532 - '@esbuild/win32-x64': 0.25.1 2475 + '@esbuild/aix-ppc64': 0.27.0 2476 + '@esbuild/android-arm': 0.27.0 2477 + '@esbuild/android-arm64': 0.27.0 2478 + '@esbuild/android-x64': 0.27.0 2479 + '@esbuild/darwin-arm64': 0.27.0 2480 + '@esbuild/darwin-x64': 0.27.0 2481 + '@esbuild/freebsd-arm64': 0.27.0 2482 + '@esbuild/freebsd-x64': 0.27.0 2483 + '@esbuild/linux-arm': 0.27.0 2484 + '@esbuild/linux-arm64': 0.27.0 2485 + '@esbuild/linux-ia32': 0.27.0 2486 + '@esbuild/linux-loong64': 0.27.0 2487 + '@esbuild/linux-mips64el': 0.27.0 2488 + '@esbuild/linux-ppc64': 0.27.0 2489 + '@esbuild/linux-riscv64': 0.27.0 2490 + '@esbuild/linux-s390x': 0.27.0 2491 + '@esbuild/linux-x64': 0.27.0 2492 + '@esbuild/netbsd-arm64': 0.27.0 2493 + '@esbuild/netbsd-x64': 0.27.0 2494 + '@esbuild/openbsd-arm64': 0.27.0 2495 + '@esbuild/openbsd-x64': 0.27.0 2496 + '@esbuild/openharmony-arm64': 0.27.0 2497 + '@esbuild/sunos-x64': 0.27.0 2498 + '@esbuild/win32-arm64': 0.27.0 2499 + '@esbuild/win32-ia32': 0.27.0 2500 + '@esbuild/win32-x64': 0.27.0 2533 2501 2534 2502 escalade@3.2.0: {} 2535 2503 2536 - exit-hook@2.2.1: {} 2504 + esm-env@1.2.2: {} 2537 2505 2538 - exsolve@1.0.4: {} 2506 + exit-hook@2.2.1: {} 2539 2507 2540 2508 fast-glob@3.3.3: 2541 2509 dependencies: ··· 2548 2516 fastq@1.19.1: 2549 2517 dependencies: 2550 2518 reusify: 1.1.0 2519 + 2520 + fdir@6.5.0(picomatch@4.0.3): 2521 + optionalDependencies: 2522 + picomatch: 4.0.3 2551 2523 2552 2524 fetch-blob@3.2.0: 2553 2525 dependencies: ··· 2559 2531 dependencies: 2560 2532 to-regex-range: 5.0.1 2561 2533 2562 - foreground-child@3.3.1: 2563 - dependencies: 2564 - cross-spawn: 7.0.6 2565 - signal-exit: 4.1.0 2566 - 2567 - fraction.js@4.3.7: {} 2534 + fraction.js@5.3.4: {} 2568 2535 2569 2536 fsevents@2.3.3: 2570 2537 optional: true ··· 2573 2540 2574 2541 gensync@1.0.0-beta.2: {} 2575 2542 2576 - get-source@2.0.12: 2577 - dependencies: 2578 - data-uri-to-buffer: 2.0.2 2579 - source-map: 0.6.1 2580 - 2581 2543 glob-parent@5.1.2: 2582 2544 dependencies: 2583 2545 is-glob: 4.0.3 ··· 2588 2550 2589 2551 glob-to-regexp@0.4.1: {} 2590 2552 2591 - glob@10.4.5: 2592 - dependencies: 2593 - foreground-child: 3.3.1 2594 - jackspeak: 3.4.3 2595 - minimatch: 9.0.5 2596 - minipass: 7.1.2 2597 - package-json-from-dist: 1.0.1 2598 - path-scurry: 1.11.1 2599 - 2600 - globals@11.12.0: {} 2601 - 2602 2553 hasown@2.0.2: 2603 2554 dependencies: 2604 2555 function-bind: 1.1.2 2605 2556 2606 2557 html-entities@2.3.3: {} 2607 2558 2608 - is-arrayish@0.3.2: 2609 - optional: true 2559 + is-arrayish@0.3.4: {} 2610 2560 2611 2561 is-binary-path@2.1.0: 2612 2562 dependencies: ··· 2618 2568 2619 2569 is-extglob@2.1.1: {} 2620 2570 2621 - is-fullwidth-code-point@3.0.0: {} 2622 - 2623 2571 is-glob@4.0.3: 2624 2572 dependencies: 2625 2573 is-extglob: 2.1.1 ··· 2628 2576 2629 2577 is-what@4.1.16: {} 2630 2578 2631 - isexe@2.0.0: {} 2632 - 2633 - jackspeak@3.4.3: 2634 - dependencies: 2635 - '@isaacs/cliui': 8.0.2 2636 - optionalDependencies: 2637 - '@pkgjs/parseargs': 0.11.0 2638 - 2639 2579 jiti@1.21.7: {} 2640 2580 2641 2581 js-tokens@4.0.0: {} ··· 2644 2584 2645 2585 json5@2.2.3: {} 2646 2586 2587 + kleur@4.1.5: {} 2588 + 2647 2589 lilconfig@3.1.3: {} 2648 2590 2649 2591 lines-and-columns@1.2.4: {} 2650 - 2651 - lru-cache@10.4.3: {} 2652 2592 2653 2593 lru-cache@5.1.1: 2654 2594 dependencies: ··· 2669 2609 2670 2610 mini-svg-data-uri@1.4.4: {} 2671 2611 2672 - miniflare@4.20250320.0: 2612 + miniflare@4.20251202.1: 2673 2613 dependencies: 2674 2614 '@cspotcode/source-map-support': 0.8.1 2675 2615 acorn: 8.14.0 2676 2616 acorn-walk: 8.3.2 2677 2617 exit-hook: 2.2.1 2678 2618 glob-to-regexp: 0.4.1 2619 + sharp: 0.33.5 2679 2620 stoppable: 1.1.0 2680 - undici: 5.29.0 2681 - workerd: 1.20250320.0 2621 + undici: 7.14.0 2622 + workerd: 1.20251202.0 2682 2623 ws: 8.18.0 2683 - youch: 3.2.3 2624 + youch: 4.1.0-beta.10 2684 2625 zod: 3.22.3 2685 2626 transitivePeerDependencies: 2686 2627 - bufferutil 2687 2628 - utf-8-validate 2688 2629 2689 - minimatch@9.0.5: 2690 - dependencies: 2691 - brace-expansion: 2.0.1 2692 - 2693 - minipass@7.1.2: {} 2694 - 2695 2630 ms@2.1.3: {} 2696 - 2697 - mustache@4.2.0: {} 2698 2631 2699 2632 mz@2.7.0: 2700 2633 dependencies: ··· 2704 2637 2705 2638 nanoid@3.3.11: {} 2706 2639 2707 - nanoid@5.1.5: {} 2640 + nanoid@5.1.6: {} 2708 2641 2709 2642 native-file-system-adapter@3.0.1: 2710 2643 optionalDependencies: ··· 2713 2646 node-domexception@1.0.0: 2714 2647 optional: true 2715 2648 2716 - node-releases@2.0.19: {} 2649 + node-releases@2.0.27: {} 2717 2650 2718 2651 normalize-path@3.0.0: {} 2719 2652 ··· 2723 2656 2724 2657 object-hash@3.0.0: {} 2725 2658 2726 - ohash@2.0.11: {} 2727 - 2728 - package-json-from-dist@1.0.1: {} 2729 - 2730 - parse5@7.2.1: 2659 + parse5@7.3.0: 2731 2660 dependencies: 2732 - entities: 4.5.0 2733 - 2734 - path-key@3.1.1: {} 2661 + entities: 6.0.1 2735 2662 2736 2663 path-parse@1.0.7: {} 2737 - 2738 - path-scurry@1.11.1: 2739 - dependencies: 2740 - lru-cache: 10.4.3 2741 - minipass: 7.1.2 2742 2664 2743 2665 path-to-regexp@6.3.0: {} 2744 2666 ··· 2748 2670 2749 2671 picomatch@2.3.1: {} 2750 2672 2673 + picomatch@4.0.3: {} 2674 + 2751 2675 pify@2.3.0: {} 2752 2676 2753 - pirates@4.0.6: {} 2677 + pirates@4.0.7: {} 2754 2678 2755 - postcss-import@15.1.0(postcss@8.5.3): 2679 + postcss-import@15.1.0(postcss@8.5.6): 2756 2680 dependencies: 2757 - postcss: 8.5.3 2681 + postcss: 8.5.6 2758 2682 postcss-value-parser: 4.2.0 2759 2683 read-cache: 1.0.0 2760 - resolve: 1.22.10 2684 + resolve: 1.22.11 2761 2685 2762 - postcss-js@4.0.1(postcss@8.5.3): 2686 + postcss-js@4.1.0(postcss@8.5.6): 2763 2687 dependencies: 2764 2688 camelcase-css: 2.0.1 2765 - postcss: 8.5.3 2689 + postcss: 8.5.6 2766 2690 2767 - postcss-load-config@4.0.2(postcss@8.5.3): 2691 + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6): 2768 2692 dependencies: 2769 2693 lilconfig: 3.1.3 2770 - yaml: 2.7.0 2771 2694 optionalDependencies: 2772 - postcss: 8.5.3 2695 + jiti: 1.21.7 2696 + postcss: 8.5.6 2773 2697 2774 - postcss-nested@6.2.0(postcss@8.5.3): 2698 + postcss-nested@6.2.0(postcss@8.5.6): 2775 2699 dependencies: 2776 - postcss: 8.5.3 2700 + postcss: 8.5.6 2777 2701 postcss-selector-parser: 6.1.2 2778 2702 2779 2703 postcss-selector-parser@6.1.2: ··· 2783 2707 2784 2708 postcss-value-parser@4.2.0: {} 2785 2709 2786 - postcss@8.5.3: 2710 + postcss@8.5.6: 2787 2711 dependencies: 2788 2712 nanoid: 3.3.11 2789 2713 picocolors: 1.1.1 2790 2714 source-map-js: 1.2.1 2791 2715 2792 - prettier-plugin-tailwindcss@0.6.11(prettier@3.5.3): 2716 + prettier-plugin-tailwindcss@0.6.14(prettier@3.7.4): 2793 2717 dependencies: 2794 - prettier: 3.5.3 2718 + prettier: 3.7.4 2795 2719 2796 - prettier@3.5.3: {} 2797 - 2798 - printable-characters@1.0.42: {} 2720 + prettier@3.7.4: {} 2799 2721 2800 2722 queue-microtask@1.2.3: {} 2801 2723 ··· 2807 2729 dependencies: 2808 2730 picomatch: 2.3.1 2809 2731 2810 - resolve@1.22.10: 2732 + resolve@1.22.11: 2811 2733 dependencies: 2812 2734 is-core-module: 2.16.1 2813 2735 path-parse: 1.0.7 ··· 2815 2737 2816 2738 reusify@1.1.0: {} 2817 2739 2818 - rollup@4.37.0: 2740 + rollup@4.53.3: 2819 2741 dependencies: 2820 - '@types/estree': 1.0.6 2742 + '@types/estree': 1.0.8 2821 2743 optionalDependencies: 2822 - '@rollup/rollup-android-arm-eabi': 4.37.0 2823 - '@rollup/rollup-android-arm64': 4.37.0 2824 - '@rollup/rollup-darwin-arm64': 4.37.0 2825 - '@rollup/rollup-darwin-x64': 4.37.0 2826 - '@rollup/rollup-freebsd-arm64': 4.37.0 2827 - '@rollup/rollup-freebsd-x64': 4.37.0 2828 - '@rollup/rollup-linux-arm-gnueabihf': 4.37.0 2829 - '@rollup/rollup-linux-arm-musleabihf': 4.37.0 2830 - '@rollup/rollup-linux-arm64-gnu': 4.37.0 2831 - '@rollup/rollup-linux-arm64-musl': 4.37.0 2832 - '@rollup/rollup-linux-loongarch64-gnu': 4.37.0 2833 - '@rollup/rollup-linux-powerpc64le-gnu': 4.37.0 2834 - '@rollup/rollup-linux-riscv64-gnu': 4.37.0 2835 - '@rollup/rollup-linux-riscv64-musl': 4.37.0 2836 - '@rollup/rollup-linux-s390x-gnu': 4.37.0 2837 - '@rollup/rollup-linux-x64-gnu': 4.37.0 2838 - '@rollup/rollup-linux-x64-musl': 4.37.0 2839 - '@rollup/rollup-win32-arm64-msvc': 4.37.0 2840 - '@rollup/rollup-win32-ia32-msvc': 4.37.0 2841 - '@rollup/rollup-win32-x64-msvc': 4.37.0 2744 + '@rollup/rollup-android-arm-eabi': 4.53.3 2745 + '@rollup/rollup-android-arm64': 4.53.3 2746 + '@rollup/rollup-darwin-arm64': 4.53.3 2747 + '@rollup/rollup-darwin-x64': 4.53.3 2748 + '@rollup/rollup-freebsd-arm64': 4.53.3 2749 + '@rollup/rollup-freebsd-x64': 4.53.3 2750 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 2751 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 2752 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 2753 + '@rollup/rollup-linux-arm64-musl': 4.53.3 2754 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 2755 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 2756 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 2757 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 2758 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 2759 + '@rollup/rollup-linux-x64-gnu': 4.53.3 2760 + '@rollup/rollup-linux-x64-musl': 4.53.3 2761 + '@rollup/rollup-openharmony-arm64': 4.53.3 2762 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 2763 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 2764 + '@rollup/rollup-win32-x64-gnu': 4.53.3 2765 + '@rollup/rollup-win32-x64-msvc': 4.53.3 2842 2766 fsevents: 2.3.3 2843 2767 2844 2768 run-parallel@1.2.0: ··· 2847 2771 2848 2772 semver@6.3.1: {} 2849 2773 2850 - semver@7.7.1: 2851 - optional: true 2774 + semver@7.7.3: {} 2852 2775 2853 - seroval-plugins@1.2.1(seroval@1.2.1): 2776 + seroval-plugins@1.3.3(seroval@1.3.2): 2854 2777 dependencies: 2855 - seroval: 1.2.1 2778 + seroval: 1.3.2 2856 2779 2857 - seroval@1.2.1: {} 2780 + seroval@1.3.2: {} 2858 2781 2859 2782 sharp@0.33.5: 2860 2783 dependencies: 2861 2784 color: 4.2.3 2862 - detect-libc: 2.0.3 2863 - semver: 7.7.1 2785 + detect-libc: 2.1.2 2786 + semver: 7.7.3 2864 2787 optionalDependencies: 2865 2788 '@img/sharp-darwin-arm64': 0.33.5 2866 2789 '@img/sharp-darwin-x64': 0.33.5 ··· 2881 2804 '@img/sharp-wasm32': 0.33.5 2882 2805 '@img/sharp-win32-ia32': 0.33.5 2883 2806 '@img/sharp-win32-x64': 0.33.5 2884 - optional: true 2885 2807 2886 - shebang-command@2.0.0: 2808 + simple-swizzle@0.2.4: 2887 2809 dependencies: 2888 - shebang-regex: 3.0.0 2810 + is-arrayish: 0.3.4 2889 2811 2890 - shebang-regex@3.0.0: {} 2891 - 2892 - signal-exit@4.1.0: {} 2893 - 2894 - simple-swizzle@0.2.2: 2812 + solid-js@1.9.10: 2895 2813 dependencies: 2896 - is-arrayish: 0.3.2 2897 - optional: true 2814 + csstype: 3.2.3 2815 + seroval: 1.3.2 2816 + seroval-plugins: 1.3.3(seroval@1.3.2) 2898 2817 2899 - solid-js@1.9.5: 2818 + solid-refresh@0.6.3(solid-js@1.9.10): 2900 2819 dependencies: 2901 - csstype: 3.1.3 2902 - seroval: 1.2.1 2903 - seroval-plugins: 1.2.1(seroval@1.2.1) 2904 - 2905 - solid-refresh@0.6.3(solid-js@1.9.5): 2906 - dependencies: 2907 - '@babel/generator': 7.26.10 2908 - '@babel/helper-module-imports': 7.25.9 2909 - '@babel/types': 7.26.10 2910 - solid-js: 1.9.5 2820 + '@babel/generator': 7.28.5 2821 + '@babel/helper-module-imports': 7.27.1 2822 + '@babel/types': 7.28.5 2823 + solid-js: 1.9.10 2911 2824 transitivePeerDependencies: 2912 2825 - supports-color 2913 2826 ··· 2920 2833 2921 2834 source-map@0.6.1: {} 2922 2835 2923 - stacktracey@2.1.8: 2924 - dependencies: 2925 - as-table: 1.0.55 2926 - get-source: 2.0.12 2927 - 2928 2836 stoppable@1.1.0: {} 2929 2837 2930 - string-width@4.2.3: 2931 - dependencies: 2932 - emoji-regex: 8.0.0 2933 - is-fullwidth-code-point: 3.0.0 2934 - strip-ansi: 6.0.1 2935 - 2936 - string-width@5.1.2: 2937 - dependencies: 2938 - eastasianwidth: 0.2.0 2939 - emoji-regex: 9.2.2 2940 - strip-ansi: 7.1.0 2941 - 2942 - strip-ansi@6.0.1: 2943 - dependencies: 2944 - ansi-regex: 5.0.1 2945 - 2946 - strip-ansi@7.1.0: 2838 + sucrase@3.35.1: 2947 2839 dependencies: 2948 - ansi-regex: 6.1.0 2949 - 2950 - sucrase@3.35.0: 2951 - dependencies: 2952 - '@jridgewell/gen-mapping': 0.3.8 2840 + '@jridgewell/gen-mapping': 0.3.13 2953 2841 commander: 4.1.1 2954 - glob: 10.4.5 2955 2842 lines-and-columns: 1.2.4 2956 2843 mz: 2.7.0 2957 - pirates: 4.0.6 2844 + pirates: 4.0.7 2845 + tinyglobby: 0.2.15 2958 2846 ts-interface-checker: 0.1.13 2847 + 2848 + supports-color@10.2.2: {} 2959 2849 2960 2850 supports-preserve-symlinks-flag@1.0.0: {} 2961 2851 2962 - tailwindcss@3.4.17: 2852 + tailwindcss@3.4.18: 2963 2853 dependencies: 2964 2854 '@alloc/quick-lru': 5.2.0 2965 2855 arg: 5.0.2 ··· 2975 2865 normalize-path: 3.0.0 2976 2866 object-hash: 3.0.0 2977 2867 picocolors: 1.1.1 2978 - postcss: 8.5.3 2979 - postcss-import: 15.1.0(postcss@8.5.3) 2980 - postcss-js: 4.0.1(postcss@8.5.3) 2981 - postcss-load-config: 4.0.2(postcss@8.5.3) 2982 - postcss-nested: 6.2.0(postcss@8.5.3) 2868 + postcss: 8.5.6 2869 + postcss-import: 15.1.0(postcss@8.5.6) 2870 + postcss-js: 4.1.0(postcss@8.5.6) 2871 + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6) 2872 + postcss-nested: 6.2.0(postcss@8.5.6) 2983 2873 postcss-selector-parser: 6.1.2 2984 - resolve: 1.22.10 2985 - sucrase: 3.35.0 2874 + resolve: 1.22.11 2875 + sucrase: 3.35.1 2986 2876 transitivePeerDependencies: 2987 - - ts-node 2877 + - tsx 2878 + - yaml 2988 2879 2989 - terser@5.39.0: 2880 + terser@5.44.1: 2990 2881 dependencies: 2991 - '@jridgewell/source-map': 0.3.6 2992 - acorn: 8.14.1 2882 + '@jridgewell/source-map': 0.3.11 2883 + acorn: 8.15.0 2993 2884 commander: 2.20.3 2994 2885 source-map-support: 0.5.21 2995 2886 ··· 3001 2892 dependencies: 3002 2893 any-promise: 1.3.0 3003 2894 2895 + tinyglobby@0.2.15: 2896 + dependencies: 2897 + fdir: 6.5.0(picomatch@4.0.3) 2898 + picomatch: 4.0.3 2899 + 3004 2900 to-regex-range@5.0.1: 3005 2901 dependencies: 3006 2902 is-number: 7.0.0 ··· 3010 2906 tslib@2.8.1: 3011 2907 optional: true 3012 2908 3013 - typescript@5.8.2: {} 2909 + typescript@5.9.3: {} 3014 2910 3015 - ufo@1.5.4: {} 3016 - 3017 - undici-types@6.20.0: {} 2911 + undici-types@6.21.0: {} 3018 2912 3019 - undici@5.29.0: 3020 - dependencies: 3021 - '@fastify/busboy': 2.1.1 2913 + undici@7.14.0: {} 3022 2914 3023 - unenv@2.0.0-rc.15: 2915 + unenv@2.0.0-rc.24: 3024 2916 dependencies: 3025 - defu: 6.1.4 3026 - exsolve: 1.0.4 3027 - ohash: 2.0.11 3028 2917 pathe: 2.0.3 3029 - ufo: 1.5.4 3030 2918 3031 - update-browserslist-db@1.1.3(browserslist@4.24.4): 2919 + update-browserslist-db@1.2.2(browserslist@4.28.1): 3032 2920 dependencies: 3033 - browserslist: 4.24.4 2921 + browserslist: 4.28.1 3034 2922 escalade: 3.2.0 3035 2923 picocolors: 1.1.1 3036 2924 3037 2925 util-deprecate@1.0.2: {} 3038 2926 3039 - validate-html-nesting@1.2.2: {} 3040 - 3041 - vite-plugin-solid@2.11.6(solid-js@1.9.5)(vite@6.2.2(@types/node@22.13.12)(jiti@1.21.7)(terser@5.39.0)(yaml@2.7.0)): 2927 + vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@22.19.2)(jiti@1.21.7)(terser@5.44.1)): 3042 2928 dependencies: 3043 - '@babel/core': 7.26.10 2929 + '@babel/core': 7.28.5 3044 2930 '@types/babel__core': 7.20.5 3045 - babel-preset-solid: 1.9.5(@babel/core@7.26.10) 2931 + babel-preset-solid: 1.9.10(@babel/core@7.28.5)(solid-js@1.9.10) 3046 2932 merge-anything: 5.1.7 3047 - solid-js: 1.9.5 3048 - solid-refresh: 0.6.3(solid-js@1.9.5) 3049 - vite: 6.2.2(@types/node@22.13.12)(jiti@1.21.7)(terser@5.39.0)(yaml@2.7.0) 3050 - vitefu: 1.0.6(vite@6.2.2(@types/node@22.13.12)(jiti@1.21.7)(terser@5.39.0)(yaml@2.7.0)) 2933 + solid-js: 1.9.10 2934 + solid-refresh: 0.6.3(solid-js@1.9.10) 2935 + vite: 7.2.7(@types/node@22.19.2)(jiti@1.21.7)(terser@5.44.1) 2936 + vitefu: 1.1.1(vite@7.2.7(@types/node@22.19.2)(jiti@1.21.7)(terser@5.44.1)) 3051 2937 transitivePeerDependencies: 3052 2938 - supports-color 3053 2939 3054 - vite@6.2.2(@types/node@22.13.12)(jiti@1.21.7)(terser@5.39.0)(yaml@2.7.0): 2940 + vite@7.2.7(@types/node@22.19.2)(jiti@1.21.7)(terser@5.44.1): 3055 2941 dependencies: 3056 - esbuild: 0.25.1 3057 - postcss: 8.5.3 3058 - rollup: 4.37.0 2942 + esbuild: 0.25.12 2943 + fdir: 6.5.0(picomatch@4.0.3) 2944 + picomatch: 4.0.3 2945 + postcss: 8.5.6 2946 + rollup: 4.53.3 2947 + tinyglobby: 0.2.15 3059 2948 optionalDependencies: 3060 - '@types/node': 22.13.12 2949 + '@types/node': 22.19.2 3061 2950 fsevents: 2.3.3 3062 2951 jiti: 1.21.7 3063 - terser: 5.39.0 3064 - yaml: 2.7.0 2952 + terser: 5.44.1 3065 2953 3066 - vitefu@1.0.6(vite@6.2.2(@types/node@22.13.12)(jiti@1.21.7)(terser@5.39.0)(yaml@2.7.0)): 2954 + vitefu@1.1.1(vite@7.2.7(@types/node@22.19.2)(jiti@1.21.7)(terser@5.44.1)): 3067 2955 optionalDependencies: 3068 - vite: 6.2.2(@types/node@22.13.12)(jiti@1.21.7)(terser@5.39.0)(yaml@2.7.0) 2956 + vite: 7.2.7(@types/node@22.19.2)(jiti@1.21.7)(terser@5.44.1) 3069 2957 3070 2958 web-streams-polyfill@3.3.3: 3071 2959 optional: true 3072 2960 3073 - which@2.0.2: 3074 - dependencies: 3075 - isexe: 2.0.0 3076 - 3077 - workerd@1.20250320.0: 2961 + workerd@1.20251202.0: 3078 2962 optionalDependencies: 3079 - '@cloudflare/workerd-darwin-64': 1.20250320.0 3080 - '@cloudflare/workerd-darwin-arm64': 1.20250320.0 3081 - '@cloudflare/workerd-linux-64': 1.20250320.0 3082 - '@cloudflare/workerd-linux-arm64': 1.20250320.0 3083 - '@cloudflare/workerd-windows-64': 1.20250320.0 2963 + '@cloudflare/workerd-darwin-64': 1.20251202.0 2964 + '@cloudflare/workerd-darwin-arm64': 1.20251202.0 2965 + '@cloudflare/workerd-linux-64': 1.20251202.0 2966 + '@cloudflare/workerd-linux-arm64': 1.20251202.0 2967 + '@cloudflare/workerd-windows-64': 1.20251202.0 3084 2968 3085 - wrangler@4.4.0: 2969 + wrangler@4.53.0: 3086 2970 dependencies: 3087 - '@cloudflare/kv-asset-handler': 0.4.0 3088 - '@cloudflare/unenv-preset': 2.3.0(unenv@2.0.0-rc.15)(workerd@1.20250320.0) 2971 + '@cloudflare/kv-asset-handler': 0.4.1 2972 + '@cloudflare/unenv-preset': 2.7.13(unenv@2.0.0-rc.24)(workerd@1.20251202.0) 3089 2973 blake3-wasm: 2.1.5 3090 - esbuild: 0.24.2 3091 - miniflare: 4.20250320.0 2974 + esbuild: 0.27.0 2975 + miniflare: 4.20251202.1 3092 2976 path-to-regexp: 6.3.0 3093 - unenv: 2.0.0-rc.15 3094 - workerd: 1.20250320.0 2977 + unenv: 2.0.0-rc.24 2978 + workerd: 1.20251202.0 3095 2979 optionalDependencies: 3096 2980 fsevents: 2.3.3 3097 - sharp: 0.33.5 3098 2981 transitivePeerDependencies: 3099 2982 - bufferutil 3100 2983 - utf-8-validate 3101 2984 3102 - wrap-ansi@7.0.0: 3103 - dependencies: 3104 - ansi-styles: 4.3.0 3105 - string-width: 4.2.3 3106 - strip-ansi: 6.0.1 3107 - 3108 - wrap-ansi@8.1.0: 3109 - dependencies: 3110 - ansi-styles: 6.2.1 3111 - string-width: 5.1.2 3112 - strip-ansi: 7.1.0 3113 - 3114 2985 ws@8.18.0: {} 3115 2986 3116 2987 yallist@3.1.1: {} 3117 2988 3118 - yaml@2.7.0: {} 2989 + youch-core@0.3.3: 2990 + dependencies: 2991 + '@poppinss/exception': 1.2.2 2992 + error-stack-parser-es: 1.0.5 3119 2993 3120 - youch@3.2.3: 2994 + youch@4.1.0-beta.10: 3121 2995 dependencies: 3122 - cookie: 0.5.0 3123 - mustache: 4.2.0 3124 - stacktracey: 2.1.8 2996 + '@poppinss/colors': 4.1.5 2997 + '@poppinss/dumper': 0.6.5 2998 + '@speed-highlight/core': 1.2.12 2999 + cookie: 1.1.1 3000 + youch-core: 0.3.3 3125 3001 3126 3002 zod@3.22.3: {}
+2
pnpm-workspace.yaml
··· 1 + onlyBuiltDependencies: 2 + - esbuild
+2 -1
src/api/queries/did-doc.ts
··· 1 - import type { AtprotoDid, DidDocument } from '@atcute/identity'; 1 + import type { DidDocument } from '@atcute/identity'; 2 2 import { 3 3 CompositeDidDocumentResolver, 4 4 PlcDidDocumentResolver, 5 5 WebDidDocumentResolver, 6 6 } from '@atcute/identity-resolver'; 7 + import type { AtprotoDid } from '@atcute/lexicons/syntax'; 7 8 8 9 const didDocumentResolver = new CompositeDidDocumentResolver({ 9 10 methods: {
+1 -1
src/api/queries/handle.ts
··· 1 - import { type AtprotoDid, type Handle, isHandle } from '@atcute/identity'; 2 1 import { XrpcHandleResolver } from '@atcute/identity-resolver'; 2 + import { type AtprotoDid, type Handle, isHandle } from '@atcute/lexicons/syntax'; 3 3 4 4 const handleResolver = new XrpcHandleResolver({ 5 5 serviceUrl: import.meta.env.VITE_APPVIEW_URL,
+1 -1
src/api/queries/plc.ts
··· 1 1 import { defs } from '@atcute/did-plc'; 2 - import { Did } from '@atcute/identity'; 2 + import type { Did } from '@atcute/lexicons/syntax'; 3 3 4 4 export const getPlcAuditLogs = async ({ did, signal }: { did: Did<'plc'>; signal?: AbortSignal }) => { 5 5 const origin = import.meta.env.VITE_PLC_DIRECTORY_URL;
+2 -2
src/api/types/plc.ts
··· 1 1 import * as v from '@badrap/valita'; 2 2 3 - import { defs, UnsignedOperation } from '@atcute/did-plc'; 3 + import { defs, type UnsignedOperation } from '@atcute/did-plc'; 4 4 5 - import { ToValidator } from '../utils/valita'; 5 + import type { ToValidator } from '../utils/valita'; 6 6 import { serviceUrlString } from './strings'; 7 7 8 8 const _unsignedOperation = defs.unsignedOperation as ToValidator<UnsignedOperation>;
-53
src/api/utils/at-uri.ts
··· 1 - import { assert } from '~/lib/utils/invariant'; 2 - 3 - type Did<TMethod extends string = string> = `did:${TMethod}:${string}`; 4 - 5 - type Nsid = `${string}.${string}.${string}`; 6 - 7 - type RecordKey = string; 8 - 9 - const DID_RE = /^did:([a-z]+):([a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-])$/; 10 - 11 - const NSID_RE = 12 - /^[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?:\.[a-zA-Z](?:[a-zA-Z0-9]{0,62})?)$/; 13 - 14 - const RECORD_KEY_RE = /^(?!\.{1,2}$)[a-zA-Z0-9_~.:-]{1,512}$/; 15 - 16 - const ATURI_RE = 17 - /^at:\/\/([a-zA-Z0-9._:%-]+)(?:\/([a-zA-Z0-9-.]+)(?:\/([a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/; 18 - 19 - const isDid = (input: unknown): input is Did => { 20 - return typeof input === 'string' && input.length >= 7 && input.length <= 2048 && DID_RE.test(input); 21 - }; 22 - 23 - const isNsid = (input: unknown): input is Nsid => { 24 - return typeof input === 'string' && input.length >= 5 && input.length <= 317 && NSID_RE.test(input); 25 - }; 26 - 27 - const isRecordKey = (input: unknown): input is RecordKey => { 28 - return typeof input === 'string' && input.length >= 1 && input.length <= 512 && RECORD_KEY_RE.test(input); 29 - }; 30 - 31 - export interface AddressedAtUri { 32 - repo: Did; 33 - collection: Nsid; 34 - rkey: string; 35 - fragment: string | undefined; 36 - } 37 - 38 - export const parseAddressedAtUri = (str: string): AddressedAtUri => { 39 - const match = ATURI_RE.exec(str); 40 - assert(match !== null, `invalid addressed-at-uri: ${str}`); 41 - 42 - const [, r, c, k, f] = match; 43 - assert(isDid(r), `invalid repo in addressed-at-uri: ${r}`); 44 - assert(isNsid(c), `invalid collection in addressed-at-uri: ${c}`); 45 - assert(isRecordKey(k), `invalid rkey in addressed-at-uri: ${k}`); 46 - 47 - return { 48 - repo: r, 49 - collection: c, 50 - rkey: k, 51 - fragment: f, 52 - }; 53 - };
+7 -12
src/api/utils/error.ts
··· 1 - import { XRPCError } from '@atcute/client'; 2 - 3 - export const formatXRPCError = (err: XRPCError): string => { 4 - const name = err.kind; 5 - return (name ? name + ': ' : '') + err.message; 6 - }; 1 + import { ClientResponseError } from '@atcute/client'; 7 2 8 3 export const formatQueryError = (err: unknown) => { 9 - if (err instanceof XRPCError) { 10 - const kind = err.kind; 4 + if (err instanceof ClientResponseError) { 5 + const error = err.error; 11 6 12 - if (kind === 'InvalidToken' || kind === 'ExpiredToken') { 7 + if (error === 'InvalidToken' || error === 'ExpiredToken') { 13 8 return `Account session is no longer valid`; 14 9 } 15 10 16 - if (kind === 'UpstreamFailure') { 11 + if (error === 'UpstreamFailure') { 17 12 return `Server appears to be experiencing issues, try again later`; 18 13 } 19 14 20 - if (kind === 'InternalServerError') { 15 + if (error === 'InternalServerError') { 21 16 return `Server is having issues processing this request, try again later`; 22 17 } 23 18 24 - return formatXRPCError(err); 19 + return err.message; 25 20 } 26 21 27 22 if (err instanceof Error) {
+69
src/api/utils/jwt.ts
··· 1 + /** 2 + * Decodes a JWT token 3 + * @param token The token string 4 + * @returns JSON object from the token 5 + */ 6 + export const decodeJwt = (token: string): unknown => { 7 + const pos = 1; 8 + const part = token.split('.')[1]; 9 + 10 + let decoded: string; 11 + 12 + if (typeof part !== 'string') { 13 + throw new Error('invalid token: missing part ' + (pos + 1)); 14 + } 15 + 16 + try { 17 + decoded = base64UrlDecode(part); 18 + } catch (e) { 19 + throw new Error('invalid token: invalid b64 for part ' + (pos + 1) + ' (' + (e as Error).message + ')'); 20 + } 21 + 22 + try { 23 + return JSON.parse(decoded); 24 + } catch (e) { 25 + throw new Error('invalid token: invalid json for part ' + (pos + 1) + ' (' + (e as Error).message + ')'); 26 + } 27 + }; 28 + 29 + /** 30 + * Decodes a URL-safe Base64 string 31 + * @param str URL-safe Base64 that needed to be decoded 32 + * @returns The actual string 33 + */ 34 + const base64UrlDecode = (str: string): string => { 35 + let output = str.replace(/-/g, '+').replace(/_/g, '/'); 36 + 37 + switch (output.length % 4) { 38 + case 0: 39 + break; 40 + case 2: 41 + output += '=='; 42 + break; 43 + case 3: 44 + output += '='; 45 + break; 46 + default: 47 + throw new Error('base64 string is not of the correct length'); 48 + } 49 + 50 + try { 51 + return b64DecodeUnicode(output); 52 + } catch { 53 + return atob(output); 54 + } 55 + }; 56 + 57 + const b64DecodeUnicode = (str: string): string => { 58 + return decodeURIComponent( 59 + atob(str).replace(/(.)/g, (_m, p) => { 60 + let code = p.charCodeAt(0).toString(16).toUpperCase(); 61 + 62 + if (code.length < 2) { 63 + code = '0' + code; 64 + } 65 + 66 + return '%' + code; 67 + }), 68 + ); 69 + };
-8
src/api/utils/strings.ts
··· 1 - import type { Records } from '@atcute/client/lexicons'; 2 - 3 - export const DID_OR_HANDLE_RE = 4 - /^[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(?:\.[a-zA-Z]{2,})$|^did:[a-z]+:[a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-]$/; 5 - 6 - export const makeAtUri = (repo: string, collection: keyof Records | (string & {}), rkey: string) => { 7 - return `at://${repo}/${collection}/${rkey}`; 8 - };
+72
src/components/accordion.tsx
··· 1 + import { createSignal, type JSX, Show } from 'solid-js'; 2 + 3 + import ChevronRightIcon from '~/components/ic-icons/baseline-chevron-right'; 4 + 5 + export interface AccordionProps { 6 + title: string; 7 + children: JSX.Element; 8 + defaultOpen?: boolean; 9 + } 10 + 11 + export const Accordion = (props: AccordionProps) => { 12 + const [isOpen, setIsOpen] = createSignal(props.defaultOpen ?? false); 13 + 14 + return ( 15 + <div class="border-b border-gray-200"> 16 + <button 17 + type="button" 18 + onClick={() => setIsOpen(!isOpen())} 19 + class="flex w-full items-center gap-3 px-4 py-3 text-left hover:bg-gray-50" 20 + > 21 + <ChevronRightIcon 22 + class={`h-5 w-5 text-gray-500 transition-transform` + (isOpen() ? ` rotate-90` : ``)} 23 + /> 24 + <span class="font-semibold">{props.title}</span> 25 + </button> 26 + 27 + <Show when={isOpen()}> 28 + <div class="pb-4 pl-12 pr-4">{props.children}</div> 29 + </Show> 30 + </div> 31 + ); 32 + }; 33 + 34 + export interface SubsectionProps { 35 + title: string; 36 + children: JSX.Element; 37 + } 38 + 39 + export const Subsection = (props: SubsectionProps) => { 40 + return ( 41 + <div class="mb-4 last:mb-0"> 42 + <h4 class="mb-3 text-sm font-semibold text-gray-600">{props.title}</h4> 43 + <div class="flex flex-col gap-3">{props.children}</div> 44 + </div> 45 + ); 46 + }; 47 + 48 + export interface StatusBadgeProps { 49 + variant: 'idle' | 'pending' | 'success' | 'error'; 50 + children: JSX.Element; 51 + } 52 + 53 + export const StatusBadge = (props: StatusBadgeProps) => { 54 + const variantStyles = () => { 55 + switch (props.variant) { 56 + case 'idle': 57 + return 'bg-gray-100 text-gray-600'; 58 + case 'pending': 59 + return 'bg-yellow-100 text-yellow-800'; 60 + case 'success': 61 + return 'bg-green-100 text-green-800'; 62 + case 'error': 63 + return 'bg-red-100 text-red-800'; 64 + } 65 + }; 66 + 67 + return ( 68 + <span class={`inline-flex items-center rounded px-2 py-0.5 text-xs font-medium ${variantStyles()}`}> 69 + {props.children} 70 + </span> 71 + ); 72 + };
+65
src/components/file-drop-zone.tsx
··· 1 + import type { JSX } from 'solid-js'; 2 + 3 + import { createDropZone, type CreateDropZoneOptions } from '~/lib/hooks/dropzone'; 4 + 5 + import Button from './inputs/button'; 6 + 7 + interface FileDropZoneProps { 8 + accept?: string; 9 + disabled?: boolean; 10 + onFiles: (files: File[]) => void; 11 + dataTypes?: CreateDropZoneOptions['dataTypes']; 12 + multiple?: boolean; 13 + children?: JSX.Element; 14 + } 15 + 16 + const FileDropZone = (props: FileDropZoneProps) => { 17 + const { ref: dropRef, isDropping } = createDropZone({ 18 + dataTypes: props.dataTypes, 19 + multiple: props.multiple ?? false, 20 + onDrop(files) { 21 + if (files) { 22 + props.onFiles(files); 23 + } 24 + }, 25 + }); 26 + 27 + const handleBrowse = () => { 28 + const input = document.createElement('input'); 29 + input.type = 'file'; 30 + if (props.accept) { 31 + input.accept = props.accept; 32 + } 33 + if (props.multiple) { 34 + input.multiple = true; 35 + } 36 + input.oninput = () => { 37 + const files = Array.from(input.files!); 38 + if (files.length > 0) { 39 + props.onFiles(files); 40 + } 41 + }; 42 + input.click(); 43 + }; 44 + 45 + return ( 46 + <fieldset 47 + ref={dropRef} 48 + disabled={props.disabled} 49 + class={ 50 + `relative grid place-items-center rounded border border-gray-300 px-6 py-12 disabled:opacity-50` + 51 + (props.disabled || !isDropping() ? ` bg-gray-100` : ` bg-green-100`) 52 + } 53 + > 54 + <div class="flex flex-col items-center gap-4"> 55 + <Button variant="outline" onClick={handleBrowse}> 56 + Browse files 57 + </Button> 58 + <p class="select-none font-medium text-gray-600">or drop your file here</p> 59 + </div> 60 + {props.children} 61 + </fieldset> 62 + ); 63 + }; 64 + 65 + export default FileDropZone;
+27 -12
src/components/inputs/button.tsx
··· 1 - import { JSX } from 'solid-js'; 1 + import { createMemo, type JSX } from 'solid-js'; 2 2 3 3 interface ButtonProps { 4 4 children?: JSX.Element; 5 5 disabled?: boolean; 6 - variant?: 'primary' | 'secondary'; 6 + variant?: 'primary' | 'secondary' | 'outline'; 7 7 type?: 'button' | 'submit'; 8 + href?: string; 8 9 onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>; 9 10 } 10 11 ··· 15 16 cn += ` bg-purple-800 text-white hover:bg-purple-700 active:bg-purple-700`; 16 17 } else if (variant === 'secondary') { 17 18 cn += ` bg-gray-200 text-black hover:bg-gray-300 active:bg-gray-300`; 19 + } else if (variant === 'outline') { 20 + cn += ` border border-gray-300 text-gray-800 hover:bg-gray-100 active:bg-gray-100`; 18 21 } 19 22 20 23 if (disabled) { ··· 25 28 }; 26 29 27 30 const Button = (props: ButtonProps) => { 28 - return ( 29 - <button 30 - type={props.type ?? 'button'} 31 - disabled={props.disabled} 32 - class={buttonStyles(props)} 33 - onClick={props.onClick} 34 - > 35 - {props.children} 36 - </button> 37 - ); 31 + const hasLink = createMemo(() => props.href !== undefined); 32 + 33 + return (() => { 34 + if (hasLink()) { 35 + return ( 36 + <a href={!props.disabled ? props.href : undefined} class={buttonStyles(props)}> 37 + {props.children} 38 + </a> 39 + ); 40 + } 41 + 42 + return ( 43 + <button 44 + type={props.type ?? 'button'} 45 + disabled={props.disabled} 46 + class={buttonStyles(props)} 47 + onClick={props.onClick} 48 + > 49 + {props.children} 50 + </button> 51 + ); 52 + }) as unknown as JSX.Element; 38 53 }; 39 54 40 55 export default Button;
+2 -3
src/components/inputs/multiline-input.tsx
··· 1 - import { createEffect, JSX } from 'solid-js'; 1 + import { createEffect, type JSX } from 'solid-js'; 2 2 3 3 import { createId } from '~/lib/hooks/id'; 4 4 5 - import { BoundInputEvent } from './_types'; 5 + import type { BoundInputEvent } from './_types'; 6 6 7 7 interface MultilineInputProps { 8 8 label: JSX.Element; ··· 39 39 name={props.name} 40 40 required={props.required} 41 41 autocomplete={props.autocomplete} 42 - // @ts-expect-error 43 42 autocorrect={props.autocorrect} 44 43 rows={22} 45 44 value={props.value}
+2 -2
src/components/inputs/radio-input.tsx
··· 1 - import { JSX } from 'solid-js'; 1 + import type { JSX } from 'solid-js'; 2 2 3 3 import { createId } from '~/lib/hooks/id'; 4 4 5 - import { BoundInputEvent } from './_types'; 5 + import type { BoundInputEvent } from './_types'; 6 6 7 7 interface RadioInputProps<T extends string> { 8 8 label: JSX.Element;
+2 -2
src/components/inputs/select-input.tsx
··· 1 - import { createEffect, JSX } from 'solid-js'; 1 + import { createEffect, type JSX } from 'solid-js'; 2 2 3 3 import { createId } from '~/lib/hooks/id'; 4 4 5 - import { BoundInputEvent } from './_types'; 5 + import type { BoundInputEvent } from './_types'; 6 6 7 7 interface SelectInputProps<T extends string> { 8 8 label: JSX.Element;
+5 -3
src/components/inputs/text-input.tsx
··· 1 - import { createEffect, JSX } from 'solid-js'; 1 + import { createEffect, type JSX } from 'solid-js'; 2 2 3 3 import { createId } from '~/lib/hooks/id'; 4 4 5 - import { BoundInputEvent } from './_types'; 5 + import type { BoundInputEvent } from './_types'; 6 6 7 - interface TextInputProps { 7 + export interface TextInputProps { 8 8 label: JSX.Element; 9 9 blurb?: JSX.Element; 10 10 monospace?: boolean; 11 11 type?: 'text' | 'password' | 'url' | 'email'; 12 12 name?: string; 13 13 required?: boolean; 14 + disabled?: boolean; 14 15 autocomplete?: 'off' | 'on' | 'one-time-code' | 'username'; 15 16 autocorrect?: 'off' | 'on'; 16 17 pattern?: string; ··· 55 56 id={fieldId} 56 57 name={props.name} 57 58 required={props.required} 59 + disabled={props.disabled} 58 60 autocomplete={props.autocomplete} 59 61 pattern={props.pattern} 60 62 placeholder={props.placeholder}
+1 -1
src/components/inputs/toggle-input.tsx
··· 2 2 3 3 import { createId } from '~/lib/hooks/id'; 4 4 5 - import { BoundInputEvent } from './_types'; 5 + import type { BoundInputEvent } from './_types'; 6 6 7 7 export interface ToggleInputProps { 8 8 label: string;
+22
src/components/page-header.tsx
··· 1 + import type { JSX } from 'solid-js'; 2 + 3 + interface PageHeaderProps { 4 + title: string; 5 + subtitle?: string; 6 + children?: JSX.Element; 7 + } 8 + 9 + const PageHeader = (props: PageHeaderProps) => { 10 + return ( 11 + <> 12 + <div class="p-4"> 13 + <h1 class="text-lg font-bold text-purple-800">{props.title}</h1> 14 + {props.subtitle && <p class="text-gray-600">{props.subtitle}</p>} 15 + {props.children} 16 + </div> 17 + <hr class="mx-4 border-gray-300" /> 18 + </> 19 + ); 20 + }; 21 + 22 + export default PageHeader;
+1 -1
src/components/wizard.tsx
··· 1 - import { Component, createMemo, createSignal, For, JSX } from 'solid-js'; 1 + import { type Component, createMemo, createSignal, For, type JSX } from 'solid-js'; 2 2 3 3 type EmptyObjectKeys<T> = { 4 4 [K in keyof T]: T[K] extends Record<string, never> ? K : never;
+13 -12
src/components/wizards/bluesky-login-step.tsx
··· 1 1 import { batch, createSignal, Match, Show, Switch } from 'solid-js'; 2 2 3 - import { CredentialManager, XRPCError } from '@atcute/client'; 4 - import { type AtprotoDid, type DidDocument, getPdsEndpoint, isAtprotoDid, isHandle } from '@atcute/identity'; 3 + import { ClientResponseError, CredentialManager } from '@atcute/client'; 4 + import { getPdsEndpoint, isAtprotoDid, type DidDocument } from '@atcute/identity'; 5 + import { isHandle, type AtprotoDid } from '@atcute/lexicons/syntax'; 5 6 6 7 import { getDidDocument } from '~/api/queries/did-doc'; 7 8 import { resolveHandleViaAppView } from '~/api/queries/handle'; ··· 88 89 setIsTotpRequired(false); 89 90 }); 90 91 }, 91 - onError(error) { 92 + onError(err) { 92 93 let message: string | undefined; 93 94 94 - if (error instanceof XRPCError) { 95 - if (error.kind === 'AuthFactorTokenRequired') { 95 + if (err instanceof ClientResponseError) { 96 + if (err.error === 'AuthFactorTokenRequired') { 96 97 setOtp(''); 97 98 setIsTotpRequired(true); 98 99 return; 99 100 } 100 101 101 - if (error.kind === 'AuthenticationRequired') { 102 + if (err.error === 'AuthenticationRequired') { 102 103 message = `Invalid identifier or password`; 103 - } else if (error.kind === 'AccountTakedown') { 104 + } else if (err.error === 'AccountTakedown') { 104 105 message = `Account has been taken down`; 105 - } else if (error.message.includes('Token is invalid')) { 106 + } else if (err.message.includes('Token is invalid')) { 106 107 message = `Invalid one-time confirmation code`; 107 108 setIsTotpRequired(true); 108 109 } 109 - } else if (error instanceof InsufficientLoginError) { 110 - message = error.message; 110 + } else if (err instanceof InsufficientLoginError) { 111 + message = err.message; 111 112 } 112 113 113 114 if (message !== undefined) { 114 115 setError(message); 115 116 } else { 116 - console.error(error); 117 - setError(`Something went wrong: ${error}`); 117 + console.error(err); 118 + setError(`Something went wrong: ${err}`); 118 119 } 119 120 }, 120 121 });
+2 -2
src/globals/rpc.ts
··· 1 - import { simpleFetchHandler, XRPC } from '@atcute/client'; 1 + import { Client, simpleFetchHandler } from '@atcute/client'; 2 2 3 3 const APPVIEW_URL = import.meta.env.VITE_APPVIEW_URL; 4 4 5 - export const appViewRpc = new XRPC({ handler: simpleFetchHandler({ service: APPVIEW_URL }) }); 5 + export const appViewRpc = new Client({ handler: simpleFetchHandler({ service: APPVIEW_URL }) });
+17 -44
src/lib/navigation/history.ts
··· 3 3 // Commit: 3e9dab413f4eda8d6bce565388c5ddb7aeff9f7e 4 4 // Most of the changes are just trimming it down to only include the browser 5 5 // history implementation. 6 + import { nanoid } from 'nanoid/non-secure'; 7 + 8 + import { EventEmitter } from '@mary/events'; 6 9 7 10 export type Action = 'traverse' | 'push' | 'replace' | 'update'; 8 11 ··· 111 114 let blockedPopTx: Transition | null = null; 112 115 const handlePop = () => { 113 116 if (blockedPopTx) { 114 - blockers.call(blockedPopTx); 117 + emitter.emit('block', blockedPopTx); 115 118 blockedPopTx = null; 116 119 } else { 117 120 const nextAction: Action = 'traverse'; 118 121 const nextLocation = getCurrentLocation(); 119 122 const nextIndex = nextLocation.index; 120 123 121 - if (blockers.length) { 124 + if (emitter.has('block')) { 122 125 if (nextIndex != null) { 123 126 const delta = location.index - nextIndex; 124 127 if (delta) { ··· 154 157 } 155 158 }; 156 159 157 - const listeners = createEvents<Listener>(); 158 - const blockers = createEvents<Blocker>(); 160 + const emitter = new EventEmitter<{ 161 + update: [evt: Update]; 162 + block: [tx: Transition]; 163 + }>(); 159 164 160 165 let location = getCurrentLocation(); 161 166 ··· 194 199 }; 195 200 196 201 const allowTx = (action: Action, location: Location, retry: () => void): boolean => { 197 - return !blockers.length || (blockers.call({ action, location, retry }), false); 202 + return !emitter.emit('block', { action, location, retry }); 198 203 }; 199 204 200 205 const applyTx = (nextAction: Action): void => { 201 206 location = getCurrentLocation(); 202 - listeners.call({ action: nextAction, location }); 207 + emitter.emit('update', { action: nextAction, location }); 203 208 }; 204 209 205 210 const navigate = (to: To, { replace, state }: NavigateOptions = {}): void => { ··· 263 268 return go(1); 264 269 }, 265 270 listen: (listener) => { 266 - return listeners.push(listener); 271 + return emitter.on('update', listener); 267 272 }, 268 273 block: (blocker) => { 269 - const unblock = blockers.push(blocker); 270 - 271 - if (blockers.length === 1) { 274 + if (!emitter.has('block')) { 272 275 window.addEventListener(BeforeUnloadEventType, promptBeforeUnload); 273 276 } 274 277 278 + const unblock = emitter.on('block', blocker); 279 + 275 280 return () => { 276 281 unblock(); 277 282 278 283 // Remove the beforeunload listener so the document may 279 284 // still be salvageable in the pagehide event. 280 285 // See https://html.spec.whatwg.org/#unloading-documents 281 - if (!blockers.length) { 286 + if (!emitter.has('block')) { 282 287 window.removeEventListener(BeforeUnloadEventType, promptBeforeUnload); 283 288 } 284 289 }; ··· 293 298 event.preventDefault(); 294 299 }; 295 300 296 - interface Events<F extends (arg: any) => void> { 297 - length: number; 298 - push: (fn: F) => () => void; 299 - call: (arg: Parameters<F>[0]) => void; 300 - } 301 - 302 - const createEvents = <F extends (arg: any) => void>(): Events<F> => { 303 - const handlers: F[] = []; 304 - 305 - return { 306 - get length() { 307 - return handlers.length; 308 - }, 309 - push(fn: F) { 310 - handlers.push(fn); 311 - 312 - return () => { 313 - const index = handlers.indexOf(fn); 314 - 315 - if (index !== -1) { 316 - handlers.splice(index, 1); 317 - } 318 - }; 319 - }, 320 - call(arg) { 321 - for (let idx = 0, len = handlers.length; idx < len; idx++) { 322 - (0, handlers[idx])(arg); 323 - } 324 - }, 325 - }; 326 - }; 327 - 328 301 const createKey = () => { 329 - return crypto.randomUUID(); 302 + return nanoid(); 330 303 }; 331 304 332 305 /**
+56 -41
src/lib/navigation/router.tsx
··· 1 1 /* @refresh reload */ 2 2 import { 3 + type Accessor, 3 4 type Component, 4 5 For, 5 6 type JSX, ··· 11 12 createSignal, 12 13 getOwner, 13 14 onCleanup, 15 + untrack, 14 16 useContext, 15 17 } from 'solid-js'; 16 18 import { delegateEvents } from 'solid-js/web'; ··· 57 59 58 60 export interface MatchedRouteState extends MatchedRoute { 59 61 readonly id: string; 62 + scrollPos: { x: number; y: number } | undefined; 60 63 } 61 64 62 65 interface RouterState { ··· 68 71 interface ViewContextObject { 69 72 owner: Owner | null; 70 73 route: MatchedRouteState; 74 + isActive: () => boolean; 71 75 } 72 76 73 77 let _entry: Location; ··· 86 90 enter: boolean; 87 91 } 88 92 89 - const routerEvents = new EventEmitter<{ [key: string]: (event: RouteEvent) => void }>(); 93 + const routerEvents = new EventEmitter<{ [key: string]: [event: RouteEvent] }>(); 90 94 91 95 export { routerEvents as UNSAFE_routerEvents }; 92 96 ··· 105 109 const nextKey = matched.id || _entry.key; 106 110 107 111 const isSingle = !!matched.id; 108 - const matchedState: MatchedRouteState = { ...matched, id: nextKey }; 112 + const matchedState: MatchedRouteState = { 113 + ...matched, 114 + id: nextKey, 115 + scrollPos: undefined, 116 + }; 109 117 110 118 const next: Record<string, MatchedRouteState> = { [nextKey]: matchedState }; 111 119 ··· 118 126 } 119 127 120 128 _cleanup = createRoot((cleanup) => { 121 - createEventListener; 122 - 123 129 onCleanup( 124 130 history.listen(({ action, location: nextEntry }) => { 125 131 const currentEntry = _entry; ··· 127 133 128 134 if (action !== 'update') { 129 135 const pathname = nextEntry.pathname; 130 - let matched = matchRoute(pathname); 136 + const matched = matchRoute(pathname); 131 137 132 138 if (!matched) { 133 139 return; ··· 139 145 let singles = current.singles; 140 146 let isNew = false; 141 147 148 + const prevId = current.active; 149 + 142 150 const nextId = matched.id || nextEntry.key; 143 - const matchedState: MatchedRouteState = { ...matched, id: nextId }; 151 + const matchedState: MatchedRouteState = { 152 + ...matched, 153 + id: nextId, 154 + scrollPos: undefined, 155 + }; 144 156 145 157 let nextViews: typeof views | undefined; 146 158 ··· 163 175 } 164 176 165 177 if (!matched.id) { 166 - // Add this view, if it's already present, set `shouldCall` to true 167 178 if (!(nextId in views)) { 168 179 if (nextViews) { 169 180 nextViews[nextId] = matchedState; 181 + isNew = true; 170 182 } else { 171 183 nextViews = { ...views, [nextId]: matchedState }; 172 184 isNew = true; 173 185 } 174 186 } 175 187 } else { 176 - // Add this view, if it's already present, set `shouldCall` to true 177 188 if (!(nextId in singles)) { 178 189 singles = { ...singles, [nextId]: matchedState }; 179 190 isNew = true; ··· 184 195 views = nextViews; 185 196 } 186 197 187 - routerEvents.emit(current.active, { focus: false, enter: false }); 198 + { 199 + const prev = current.views[prevId] || current.singles[prevId]; 200 + if (prev) { 201 + prev.scrollPos = { x: window.scrollX, y: window.scrollY }; 202 + } 203 + } 204 + 205 + routerEvents.emit(prevId, { focus: false, enter: false }); 206 + 188 207 setState({ active: nextId, views: views, singles: singles }); 189 208 190 - if (!isNew) { 209 + if (isNew) { 210 + // Scroll to top if we're pushing or replacing, it's a new page. 211 + window.scrollTo(0, 0); 212 + } else { 213 + { 214 + const next = views[nextId] || singles[nextId]; 215 + if (next?.scrollPos) { 216 + const pos = next.scrollPos; 217 + window.scrollTo(pos.x, pos.y); 218 + } 219 + } 220 + 191 221 routerEvents.emit(nextId, { 192 222 focus: true, 193 223 enter: action !== 'traverse' || nextEntry.index > currentEntry.index, 194 224 }); 195 - } 196 - 197 - // Scroll to top if we're pushing or replacing, it's a new page. 198 - if (!matched.id && (action === 'push' || action === 'replace')) { 199 - window.scrollTo({ top: 0, behavior: 'instant' }); 200 225 } 201 226 } 202 227 }), ··· 235 260 } 236 261 237 262 evt.preventDefault(); 238 - 239 - if (location.pathname !== pathname || location.search !== search || location.hash !== hash) { 240 - history.navigate({ pathname, search, hash }); 241 - } 263 + history.navigate({ pathname, search, hash }); 242 264 }); 243 265 244 266 return cleanup; ··· 273 295 }; 274 296 275 297 export const onRouteEnter = (cb: () => void) => { 276 - const { route } = useViewContext(); 298 + const { route, isActive } = useViewContext(); 277 299 278 - cb(); 300 + if (untrack(isActive)) { 301 + cb(); 302 + } 303 + 279 304 onCleanup(routerEvents.on(route.id, (e) => e.enter && cb())); 305 + }; 306 + 307 + export const useIsFocused = (): Accessor<boolean> => { 308 + const { isActive } = useViewContext(); 309 + 310 + return isActive; 280 311 }; 281 312 282 313 export const createFocusEffect = (cb: () => void) => { 283 - const { route } = useViewContext(); 284 - const [active, setActive] = createSignal(true); 314 + const isFocused = useIsFocused(); 285 315 286 - onCleanup(routerEvents.on(route.id, (e) => setActive(e.focus))); 287 316 createEffect(() => { 288 - if (active()) { 289 - cb(); 317 + if (isFocused()) { 318 + createEffect(cb); 290 319 } 291 320 }); 292 321 }; ··· 305 334 const render = props.render; 306 335 307 336 const renderView = (matched: MatchedRouteState) => { 308 - const def = matched.def; 309 337 const id = matched.id; 310 338 311 339 const active = createMemo((): boolean => state().active === id); ··· 313 341 const context: ViewContextObject = { 314 342 owner: getOwner(), 315 343 route: matched, 344 + isActive: active, 316 345 }; 317 - 318 - if (def.single) { 319 - let storedHeight: number | undefined; 320 - 321 - onCleanup( 322 - routerEvents.on(id, (ev) => { 323 - if (!ev.focus) { 324 - storedHeight = document.documentElement.scrollTop; 325 - } else if (storedHeight !== undefined) { 326 - window.scrollTo({ top: storedHeight, behavior: 'instant' }); 327 - } 328 - }), 329 - ); 330 - } 331 346 332 347 return ( 333 348 <Freeze freeze={!active()}>
+104 -4
src/lib/utils/confirmation-code.ts
··· 1 - import { customAlphabet } from 'nanoid'; 1 + import { sample } from '@mary/array-fns'; 2 2 3 - const generateCode = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', 10); 3 + const words = [ 4 + 'abroad', 5 + 'acorn', 6 + 'anaconda', 7 + 'anchovy', 8 + 'aorta', 9 + 'argue', 10 + 'ashy', 11 + 'astound', 12 + 'attest', 13 + 'babied', 14 + 'bobcat', 15 + 'bondless', 16 + 'bullion', 17 + 'bunny', 18 + 'celtic', 19 + 'chivalry', 20 + 'circling', 21 + 'civic', 22 + 'clobber', 23 + 'conform', 24 + 'cosmic', 25 + 'crier', 26 + 'curtly', 27 + 'depose', 28 + 'diagnosis', 29 + 'disfigure', 30 + 'drank', 31 + 'ducktail', 32 + 'eel', 33 + 'effort', 34 + 'equipment', 35 + 'eternal', 36 + 'exemplify', 37 + 'filtrate', 38 + 'fit', 39 + 'flaccid', 40 + 'fool', 41 + 'germinate', 42 + 'glade', 43 + 'graveness', 44 + 'gray', 45 + 'hydrant', 46 + 'italicize', 47 + 'landowner', 48 + 'lavender', 49 + 'mandatory', 50 + 'molecule', 51 + 'multitude', 52 + 'music', 53 + 'national', 54 + 'neatly', 55 + 'omnivore', 56 + 'other', 57 + 'overdrive', 58 + 'overhang', 59 + 'overlying', 60 + 'padded', 61 + 'pang', 62 + 'paralyses', 63 + 'partner', 64 + 'pedometer', 65 + 'plaything', 66 + 'pointy', 67 + 'prescribe', 68 + 'pueblo', 69 + 'pursuant', 70 + 'reprise', 71 + 'resilient', 72 + 'reusable', 73 + 'roster', 74 + 'scenic', 75 + 'selected', 76 + 'singer', 77 + 'slacker', 78 + 'smirk', 79 + 'smoked', 80 + 'smugly', 81 + 'startle', 82 + 'sternum', 83 + 'strut', 84 + 'subsystem', 85 + 'supper', 86 + 'swifter', 87 + 'tacking', 88 + 'traffic', 89 + 'tragedy', 90 + 'trapper', 91 + 'tummy', 92 + 'twiddle', 93 + 'unglazed', 94 + 'ungloved', 95 + 'unicorn', 96 + 'unissued', 97 + 'unmovable', 98 + 'unwary', 99 + 'uselessly', 100 + 'venus', 101 + 'vertebrae', 102 + 'wildly', 103 + 'wrecker', 104 + ]; 4 105 5 106 export const generateConfirmationCode = () => { 6 - const code = generateCode(); 7 - return `${code.slice(0, 5)}-${code.slice(5, 10)}`; 107 + return sample(words, 3).join(' '); 8 108 };
+18 -6
src/lib/utils/promise-queue.ts
··· 1 + import Queue from '@mary/ds-queue'; 2 + 3 + interface QueueTask { 4 + deferred: PromiseWithResolvers<any>; 5 + fn: () => any; 6 + } 7 + 1 8 export class PromiseQueue { 2 - #queue: { deferred: PromiseWithResolvers<any>; fn: () => any }[] = []; 9 + #queue = new Queue<QueueTask>(); 3 10 4 11 #max: number; 5 12 #current = 0; ··· 11 18 add<T>(fn: () => Promise<T>): Promise<T> { 12 19 const deferred = Promise.withResolvers<T>(); 13 20 14 - this.#queue.push({ deferred, fn }); 21 + this.#queue.enqueue({ deferred, fn }); 15 22 this.#run(); 16 23 17 24 return deferred.promise; 18 25 } 19 26 20 27 async flush(): Promise<void> { 21 - while (this.#queue.length > 0) { 22 - await Promise.all(this.#queue.map((task) => task.deferred.promise)); 28 + while (this.#queue.size > 0) { 29 + // type assertion here because JSR omits the [Symbol.iterator] method declaration 30 + await Promise.all( 31 + Array.from(this.#queue as any as Iterable<QueueTask>, (task) => task.deferred.promise), 32 + ); 23 33 } 24 34 } 25 35 26 36 #run() { 27 - if (this.#queue.length > 0 && this.#current <= this.#max) { 28 - const { deferred, fn } = this.#queue.shift()!; 37 + let task: QueueTask | undefined; 38 + 39 + if (this.#current < this.#max && (task = this.#queue.dequeue()) !== undefined) { 40 + const { deferred, fn } = task; 29 41 this.#current++; 30 42 31 43 const promise = new Promise((r) => r(fn()));
+5 -6
src/lib/utils/search-params.ts
··· 1 1 import { batch, createSignal } from 'solid-js'; 2 2 3 - import { At } from '@atcute/client/lexicons'; 4 - import { isDid, isHandle } from '@atcute/identity'; 3 + import { isDid, isHandle } from '@atcute/lexicons/syntax'; 5 4 6 - import { UnwrapArray } from '~/api/utils/types'; 5 + import type { UnwrapArray } from '~/api/utils/types'; 7 6 8 7 export interface ParamParser<T> { 9 8 parse: (value: string | string[] | null) => T | null; ··· 223 222 224 223 export const asDID = createParser({ 225 224 parse(value) { 226 - if (typeof value === 'string' && isDid(value)) { 227 - return value as At.DID; 225 + if (isDid(value)) { 226 + return value; 228 227 } 229 228 230 229 return null; ··· 236 235 237 236 export const asHandle = createParser({ 238 237 parse(value) { 239 - if (typeof value === 'string' && isHandle(value)) { 238 + if (isHandle(value)) { 240 239 return value; 241 240 } 242 241
+17
src/lib/utils/stream.ts
··· 1 + export async function* iterateStream<T>(stream: ReadableStream<T>) { 2 + const reader = stream.getReader(); 3 + 4 + try { 5 + while (true) { 6 + const { done, value } = await reader.read(); 7 + 8 + if (done) { 9 + return; 10 + } 11 + 12 + yield value; 13 + } 14 + } finally { 15 + reader.releaseLock(); 16 + } 17 + }
+3
src/main.tsx
··· 22 22 if (Symbol.dispose === undefined) { 23 23 Object.defineProperty(Symbol, 'dispose', { value: Symbol.for(`Symbol.dispose`) }); 24 24 } 25 + if (Symbol.asyncDispose === undefined) { 26 + Object.defineProperty(Symbol, 'asyncDispose', { value: Symbol.for(`Symbol.asyncDispose`) }); 27 + } 25 28 26 29 render(App, document.body);
+9
src/routes.ts
··· 22 22 path: '/crypto-generate', 23 23 component: lazy(() => import('./views/crypto/crypto-generate')), 24 24 }, 25 + { 26 + path: '/crypto-info', 27 + component: lazy(() => import('./views/crypto/crypto-info')), 28 + }, 25 29 26 30 { 27 31 path: '/did-lookup', ··· 47 51 { 48 52 path: '/repo-archive-explore', 49 53 component: lazy(() => import('./views/repository/repo-archive-explore/page')), 54 + }, 55 + 56 + { 57 + path: '/account-migrate', 58 + component: lazy(() => import('./views/account/account-migrate/page')), 50 59 }, 51 60 52 61 {
+49
src/views/account/account-migrate/context.tsx
··· 1 + import { createContext, createSignal, useContext, type JSX } from 'solid-js'; 2 + 3 + import type { CredentialManager } from '@atcute/client'; 4 + import type { DidDocument } from '@atcute/identity'; 5 + import type { AtprotoDid, Did } from '@atcute/lexicons/syntax'; 6 + 7 + export interface SourceAccount { 8 + did: AtprotoDid; 9 + didDoc: DidDocument; 10 + pdsUrl: string; 11 + manager: CredentialManager | null; 12 + } 13 + 14 + export interface DestinationAccount { 15 + pdsUrl: string; 16 + serviceDid: Did; 17 + manager: CredentialManager | null; 18 + } 19 + 20 + export interface MigrationContextValue { 21 + source: () => SourceAccount | null; 22 + setSource: (account: SourceAccount | null) => void; 23 + destination: () => DestinationAccount | null; 24 + setDestination: (account: DestinationAccount | null) => void; 25 + } 26 + 27 + const MigrationContext = createContext<MigrationContextValue>(); 28 + 29 + export const MigrationProvider = (props: { children: JSX.Element }) => { 30 + const [source, setSource] = createSignal<SourceAccount | null>(null); 31 + const [destination, setDestination] = createSignal<DestinationAccount | null>(null); 32 + 33 + const value: MigrationContextValue = { 34 + source, 35 + setSource, 36 + destination, 37 + setDestination, 38 + }; 39 + 40 + return <MigrationContext.Provider value={value}>{props.children}</MigrationContext.Provider>; 41 + }; 42 + 43 + export const useMigration = (): MigrationContextValue => { 44 + const context = useContext(MigrationContext); 45 + if (!context) { 46 + throw new Error('useMigration must be used within a MigrationProvider'); 47 + } 48 + return context; 49 + };
+54
src/views/account/account-migrate/page.tsx
··· 1 + import { createEffect, createSignal, onCleanup } from 'solid-js'; 2 + 3 + import { history } from '~/globals/navigation'; 4 + 5 + import { useTitle } from '~/lib/navigation/router'; 6 + 7 + import PageHeader from '~/components/page-header'; 8 + 9 + import { MigrationProvider } from './context'; 10 + 11 + import SourceAccountSection from './sections/source-account'; 12 + import DestinationAccountSection from './sections/destination-account'; 13 + import RepositorySection from './sections/repository'; 14 + import BlobsSection from './sections/blobs'; 15 + import PreferencesSection from './sections/preferences'; 16 + import IdentitySection from './sections/identity'; 17 + import AccountStatusSection from './sections/account-status'; 18 + 19 + const AccountMigratePage = () => { 20 + const [hasStarted, setHasStarted] = createSignal(false); 21 + 22 + createEffect(() => { 23 + if (hasStarted()) { 24 + const cleanup = history.block((tx) => { 25 + if (window.confirm(`You have a migration in progress. Leave this page?`)) { 26 + cleanup(); 27 + tx.retry(); 28 + } 29 + }); 30 + 31 + onCleanup(cleanup); 32 + } 33 + }); 34 + 35 + useTitle(() => `Migrate account โ€” boat`); 36 + 37 + return ( 38 + <MigrationProvider> 39 + <PageHeader title="Migrate account" subtitle="Move your account data to another server" /> 40 + 41 + <div class="flex flex-col"> 42 + <SourceAccountSection onStarted={() => setHasStarted(true)} /> 43 + <DestinationAccountSection /> 44 + <RepositorySection /> 45 + <BlobsSection /> 46 + <PreferencesSection /> 47 + <IdentitySection /> 48 + <AccountStatusSection /> 49 + </div> 50 + </MigrationProvider> 51 + ); 52 + }; 53 + 54 + export default AccountMigratePage;
+207
src/views/account/account-migrate/sections/account-status.tsx
··· 1 + import { Show } from 'solid-js'; 2 + 3 + import { Client, type CredentialManager, ok } from '@atcute/client'; 4 + 5 + import { createMutation } from '~/lib/utils/mutation'; 6 + 7 + import { Accordion, StatusBadge, Subsection } from '~/components/accordion'; 8 + import Button from '~/components/inputs/button'; 9 + 10 + import { useMigration } from '../context'; 11 + 12 + interface AccountStatus { 13 + activated: boolean; 14 + validDid: boolean; 15 + repoCommit: string; 16 + repoRev: string; 17 + repoBlocks: number; 18 + indexedRecords: number; 19 + privateStateValues: number; 20 + expectedBlobs: number; 21 + importedBlobs: number; 22 + } 23 + 24 + const AccountStatusSection = () => { 25 + const { source, destination } = useMigration(); 26 + 27 + const checkSourceMutation = createMutation({ 28 + async mutationFn({ manager }: { manager: CredentialManager }) { 29 + const sourceClient = new Client({ handler: manager }); 30 + return await ok(sourceClient.get('com.atproto.server.checkAccountStatus')) as AccountStatus; 31 + }, 32 + onError(err) { 33 + console.error(err); 34 + }, 35 + }); 36 + 37 + const checkDestMutation = createMutation({ 38 + async mutationFn({ manager }: { manager: CredentialManager }) { 39 + const destClient = new Client({ handler: manager }); 40 + return await ok(destClient.get('com.atproto.server.checkAccountStatus')) as AccountStatus; 41 + }, 42 + onError(err) { 43 + console.error(err); 44 + }, 45 + }); 46 + 47 + const activateMutation = createMutation({ 48 + async mutationFn({ manager }: { manager: CredentialManager }) { 49 + const destClient = new Client({ handler: manager }); 50 + await ok(destClient.post('com.atproto.server.activateAccount', { as: null })); 51 + }, 52 + onSuccess() { 53 + const dest = destination(); 54 + if (dest?.manager) { 55 + checkDestMutation.mutate({ manager: dest.manager }); 56 + } 57 + }, 58 + onError(err) { 59 + console.error(err); 60 + }, 61 + }); 62 + 63 + const deactivateMutation = createMutation({ 64 + async mutationFn({ manager }: { manager: CredentialManager }) { 65 + if (!confirm('Are you sure you want to deactivate your source account? This will prevent the old PDS from serving your data.')) { 66 + throw new Error('Cancelled'); 67 + } 68 + const sourceClient = new Client({ handler: manager }); 69 + await ok(sourceClient.post('com.atproto.server.deactivateAccount', { as: null, input: {} })); 70 + }, 71 + onSuccess() { 72 + const src = source(); 73 + if (src?.manager) { 74 + checkSourceMutation.mutate({ manager: src.manager }); 75 + } 76 + }, 77 + onError(err) { 78 + if (err instanceof Error && err.message === 'Cancelled') return; 79 + console.error(err); 80 + }, 81 + }); 82 + 83 + const renderStatus = (status: AccountStatus) => ( 84 + <div class="space-y-1 text-sm"> 85 + <p> 86 + <span class="text-gray-500">Status:</span>{' '} 87 + <StatusBadge variant={status.activated ? 'success' : 'idle'}> 88 + {status.activated ? 'Active' : 'Deactivated'} 89 + </StatusBadge> 90 + </p> 91 + <p> 92 + <span class="text-gray-500">Records:</span>{' '} 93 + <span class="font-mono">{status.indexedRecords}</span> 94 + </p> 95 + <p> 96 + <span class="text-gray-500">Blobs:</span>{' '} 97 + <span class="font-mono">{status.importedBlobs}/{status.expectedBlobs}</span> 98 + </p> 99 + <p> 100 + <span class="text-gray-500">Repo blocks:</span>{' '} 101 + <span class="font-mono">{status.repoBlocks}</span> 102 + </p> 103 + </div> 104 + ); 105 + 106 + return ( 107 + <Accordion title="Account Status"> 108 + <Subsection title="Source account"> 109 + <Show 110 + when={source()?.manager} 111 + fallback={<p class="text-sm text-gray-500">Sign in to source account first.</p>} 112 + > 113 + {(manager) => ( 114 + <> 115 + <div class="flex items-center gap-3"> 116 + <Button 117 + variant="outline" 118 + onClick={() => checkSourceMutation.mutate({ manager: manager() })} 119 + disabled={checkSourceMutation.isPending} 120 + > 121 + {checkSourceMutation.isPending ? 'Checking...' : 'Check status'} 122 + </Button> 123 + </div> 124 + 125 + <Show when={checkSourceMutation.isError}> 126 + <p class="text-sm text-red-600">{`${checkSourceMutation.error}`}</p> 127 + </Show> 128 + 129 + <Show when={checkSourceMutation.data}> 130 + {(status) => ( 131 + <> 132 + {renderStatus(status())} 133 + 134 + <Show when={status().activated}> 135 + <div class="mt-3"> 136 + <Button 137 + variant="secondary" 138 + onClick={() => deactivateMutation.mutate({ manager: manager() })} 139 + disabled={deactivateMutation.isPending} 140 + > 141 + {deactivateMutation.isPending ? 'Deactivating...' : 'Deactivate source account'} 142 + </Button> 143 + </div> 144 + </Show> 145 + </> 146 + )} 147 + </Show> 148 + </> 149 + )} 150 + </Show> 151 + </Subsection> 152 + 153 + <Subsection title="Destination account"> 154 + <Show 155 + when={destination()?.manager} 156 + fallback={<p class="text-sm text-gray-500">Sign in to destination account first.</p>} 157 + > 158 + {(manager) => ( 159 + <> 160 + <div class="flex items-center gap-3"> 161 + <Button 162 + variant="outline" 163 + onClick={() => checkDestMutation.mutate({ manager: manager() })} 164 + disabled={checkDestMutation.isPending} 165 + > 166 + {checkDestMutation.isPending ? 'Checking...' : 'Check status'} 167 + </Button> 168 + </div> 169 + 170 + <Show when={checkDestMutation.isError}> 171 + <p class="text-sm text-red-600">{`${checkDestMutation.error}`}</p> 172 + </Show> 173 + 174 + <Show when={checkDestMutation.data}> 175 + {(status) => ( 176 + <> 177 + {renderStatus(status())} 178 + 179 + <Show when={!status().activated}> 180 + <div class="mt-3"> 181 + <Button 182 + onClick={() => activateMutation.mutate({ manager: manager() })} 183 + disabled={activateMutation.isPending} 184 + > 185 + {activateMutation.isPending ? 'Activating...' : 'Activate destination account'} 186 + </Button> 187 + </div> 188 + </Show> 189 + </> 190 + )} 191 + </Show> 192 + </> 193 + )} 194 + </Show> 195 + </Subsection> 196 + 197 + <Show when={activateMutation.isError || deactivateMutation.isError}> 198 + <p class="text-sm text-red-600"> 199 + {activateMutation.isError ? `Failed to activate: ${activateMutation.error}` : ''} 200 + {deactivateMutation.isError ? `Failed to deactivate: ${deactivateMutation.error}` : ''} 201 + </p> 202 + </Show> 203 + </Accordion> 204 + ); 205 + }; 206 + 207 + export default AccountStatusSection;
+455
src/views/account/account-migrate/sections/blobs.tsx
··· 1 + import { showOpenFilePicker, showSaveFilePicker } from 'native-file-system-adapter'; 2 + import { createSignal, For, Show } from 'solid-js'; 3 + 4 + import { Client, ClientResponseError, type CredentialManager, ok, simpleFetchHandler } from '@atcute/client'; 5 + import { untar, writeTarEntry } from '@mary/tar'; 6 + 7 + import { createMutation } from '~/lib/utils/mutation'; 8 + 9 + import { Accordion, StatusBadge, Subsection } from '~/components/accordion'; 10 + import Button from '~/components/inputs/button'; 11 + 12 + import { useMigration, type SourceAccount } from '../context'; 13 + 14 + const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 15 + 16 + const BlobsSection = () => { 17 + const { source, destination } = useMigration(); 18 + 19 + // Progress state (kept separate since mutations don't handle incremental updates) 20 + const [exportProgress, setExportProgress] = createSignal<string>(); 21 + const [importProgress, setImportProgress] = createSignal<string>(); 22 + 23 + const exportMutation = createMutation({ 24 + async mutationFn({ source }: { source: SourceAccount }) { 25 + const sourceClient = new Client({ handler: simpleFetchHandler({ service: source.pdsUrl }) }); 26 + 27 + setExportProgress('Retrieving list of blobs...'); 28 + 29 + // Get list of all blobs 30 + let blobs: string[] = []; 31 + let cursor: string | undefined; 32 + do { 33 + const data = await ok( 34 + sourceClient.get('com.atproto.sync.listBlobs', { 35 + params: { did: source.did, cursor, limit: 1_000 }, 36 + }), 37 + ); 38 + cursor = data.cursor; 39 + blobs = blobs.concat(data.cids); 40 + setExportProgress(`Retrieving list of blobs (found ${blobs.length})`); 41 + } while (cursor !== undefined); 42 + 43 + if (blobs.length === 0) { 44 + return { count: 0, cancelled: false }; 45 + } 46 + 47 + setExportProgress('Waiting for file picker...'); 48 + 49 + const fd = await showSaveFilePicker({ 50 + suggestedName: `blobs-${source.did}-${new Date().toISOString()}.tar`, 51 + // @ts-expect-error: ponyfill doesn't have the full typings 52 + id: 'blob-export', 53 + startIn: 'downloads', 54 + types: [ 55 + { 56 + description: 'Tarball archive', 57 + accept: { 'application/tar': ['.tar'] }, 58 + }, 59 + ], 60 + }).catch((err) => { 61 + if (err instanceof DOMException && err.name === 'AbortError') { 62 + return undefined; 63 + } 64 + throw err; 65 + }); 66 + 67 + if (!fd) { 68 + return { count: 0, cancelled: true }; 69 + } 70 + 71 + const writable = await fd.createWritable(); 72 + 73 + let downloaded = 0; 74 + for (const cid of blobs) { 75 + setExportProgress(`Downloading blobs (${downloaded}/${blobs.length})`); 76 + 77 + const downloadBlob = async (): Promise<Uint8Array | undefined> => { 78 + let attempts = 0; 79 + while (true) { 80 + if (attempts > 0) await sleep(2_000); 81 + attempts++; 82 + 83 + try { 84 + const response = await sourceClient.get('com.atproto.sync.getBlob', { 85 + as: 'bytes', 86 + params: { did: source.did, cid }, 87 + }); 88 + 89 + if (response.ok) { 90 + return response.data; 91 + } 92 + 93 + if (response.status === 400 && response.data.message === 'Blob not found') { 94 + return undefined; 95 + } 96 + 97 + if (response.status === 429) { 98 + await sleep(10_000); 99 + } 100 + 101 + if (attempts < 3) continue; 102 + throw new ClientResponseError(response); 103 + } catch (err) { 104 + if (attempts < 3) continue; 105 + throw err; 106 + } 107 + } 108 + }; 109 + 110 + const data = await downloadBlob(); 111 + if (data !== undefined) { 112 + const entry = writeTarEntry({ filename: `blobs/${cid}`, data }); 113 + await writable.write(entry); 114 + } 115 + 116 + downloaded++; 117 + } 118 + 119 + await writable.close(); 120 + return { count: blobs.length, cancelled: false }; 121 + }, 122 + onError(err) { 123 + console.error(err); 124 + }, 125 + onSettled() { 126 + setExportProgress(); 127 + }, 128 + }); 129 + 130 + const importFromFileMutation = createMutation({ 131 + async mutationFn({ destManager }: { destManager: CredentialManager }) { 132 + setImportProgress('Waiting for file picker...'); 133 + 134 + const [fd] = await showOpenFilePicker({ 135 + // @ts-expect-error: ponyfill doesn't have the full typings 136 + id: 'blob-import', 137 + types: [ 138 + { 139 + description: 'Tarball archive', 140 + accept: { 'application/tar': ['.tar'] }, 141 + }, 142 + ], 143 + }).catch((err) => { 144 + if (err instanceof DOMException && err.name === 'AbortError') { 145 + return [undefined]; 146 + } 147 + throw err; 148 + }); 149 + 150 + if (!fd) { 151 + return { uploaded: 0, failed: 0, cancelled: true }; 152 + } 153 + 154 + setImportProgress('Reading archive...'); 155 + const file = await fd.getFile(); 156 + 157 + const destClient = new Client({ handler: destManager }); 158 + 159 + let uploaded = 0; 160 + let failed = 0; 161 + 162 + for await (const entry of untar(file.stream())) { 163 + if (entry.type !== 'file') continue; 164 + 165 + const filename = entry.name; 166 + // Extract CID from path like "blobs/bafk..." 167 + const cid = filename.split('/').pop(); 168 + if (!cid) continue; 169 + 170 + setImportProgress(`Uploading blobs (${uploaded} uploaded, ${failed} failed)`); 171 + 172 + try { 173 + const data = await entry.bytes(); 174 + await destClient.post('com.atproto.repo.uploadBlob', { 175 + input: data, 176 + headers: { 177 + 'content-type': 'application/octet-stream', 178 + }, 179 + }); 180 + uploaded++; 181 + } catch (err) { 182 + console.error(`Failed to upload blob ${cid}:`, err); 183 + failed++; 184 + } 185 + } 186 + 187 + return { uploaded, failed, cancelled: false }; 188 + }, 189 + onError(err) { 190 + console.error(err); 191 + }, 192 + onSettled() { 193 + setImportProgress(); 194 + }, 195 + }); 196 + 197 + const importFromSourceMutation = createMutation({ 198 + async mutationFn({ source, destManager }: { source: SourceAccount; destManager: CredentialManager }) { 199 + setImportProgress('Checking for missing blobs...'); 200 + 201 + const sourceClient = new Client({ handler: simpleFetchHandler({ service: source.pdsUrl }) }); 202 + const destClient = new Client({ handler: destManager }); 203 + 204 + let uploaded = 0; 205 + let failed = 0; 206 + let cursor: string | undefined; 207 + 208 + do { 209 + const data = await ok( 210 + destClient.get('com.atproto.repo.listMissingBlobs', { 211 + params: { cursor, limit: 100 }, 212 + }), 213 + ); 214 + cursor = data.cursor; 215 + 216 + for (const blob of data.blobs) { 217 + setImportProgress(`Uploading missing blobs (${uploaded} uploaded, ${failed} failed)`); 218 + 219 + try { 220 + const response = await sourceClient.get('com.atproto.sync.getBlob', { 221 + as: 'stream', 222 + params: { did: source.did, cid: blob.cid }, 223 + }); 224 + 225 + if (!response.ok) { 226 + failed++; 227 + continue; 228 + } 229 + 230 + const contentType = response.headers.get('content-type') || 'application/octet-stream'; 231 + 232 + await destClient.post('com.atproto.repo.uploadBlob', { 233 + input: response.data, 234 + headers: { 235 + 'content-type': contentType, 236 + }, 237 + }); 238 + 239 + uploaded++; 240 + } catch (err) { 241 + console.error(`Failed to transfer blob ${blob.cid}:`, err); 242 + failed++; 243 + } 244 + } 245 + } while (cursor !== undefined); 246 + 247 + return { uploaded, failed }; 248 + }, 249 + onError(err) { 250 + console.error(err); 251 + }, 252 + onSettled() { 253 + setImportProgress(); 254 + }, 255 + }); 256 + 257 + const checkStatusMutation = createMutation({ 258 + async mutationFn({ destManager }: { destManager: CredentialManager }) { 259 + const destClient = new Client({ handler: destManager }); 260 + const status = await ok(destClient.get('com.atproto.server.checkAccountStatus')); 261 + 262 + let missingBlobs: string[] = []; 263 + 264 + // Get list of missing blobs if any 265 + if (status.expectedBlobs > status.importedBlobs) { 266 + let cursor: string | undefined; 267 + do { 268 + const data = await ok( 269 + destClient.get('com.atproto.repo.listMissingBlobs', { 270 + params: { cursor, limit: 100 }, 271 + }), 272 + ); 273 + cursor = data.cursor; 274 + missingBlobs.push(...data.blobs.map((b) => b.cid)); 275 + } while (cursor !== undefined); 276 + } 277 + 278 + return { 279 + expected: status.expectedBlobs, 280 + imported: status.importedBlobs, 281 + missingBlobs, 282 + }; 283 + }, 284 + onError(err) { 285 + console.error(err); 286 + }, 287 + }); 288 + 289 + const isImporting = () => importFromFileMutation.isPending || importFromSourceMutation.isPending; 290 + 291 + const getExportStatusText = () => { 292 + const data = exportMutation.data; 293 + if (data?.cancelled) return undefined; 294 + if (data?.count === 0) return 'No blobs to export'; 295 + if (data) return `Exported ${data.count} blobs`; 296 + return exportProgress(); 297 + }; 298 + 299 + const getImportStatusText = () => { 300 + const fileData = importFromFileMutation.data; 301 + const sourceData = importFromSourceMutation.data; 302 + 303 + if (fileData && !fileData.cancelled) { 304 + return ( 305 + `Uploaded ${fileData.uploaded} blobs` + (fileData.failed > 0 ? ` (${fileData.failed} failed)` : '') 306 + ); 307 + } 308 + if (sourceData) { 309 + if (sourceData.uploaded === 0 && sourceData.failed === 0) return 'No missing blobs'; 310 + return ( 311 + `Uploaded ${sourceData.uploaded} blobs` + 312 + (sourceData.failed > 0 ? ` (${sourceData.failed} failed)` : '') 313 + ); 314 + } 315 + return importProgress(); 316 + }; 317 + 318 + const getImportError = () => importFromFileMutation.error || importFromSourceMutation.error; 319 + 320 + return ( 321 + <Accordion title="Blobs"> 322 + <Subsection title="Export from source"> 323 + <p class="text-sm text-gray-600">Download all blobs as a tarball for backup or manual import.</p> 324 + 325 + <Show when={source()} fallback={<p class="text-sm text-gray-500">Resolve source account first.</p>}> 326 + {(src) => ( 327 + <> 328 + <div class="flex items-center gap-3"> 329 + <Button 330 + onClick={() => exportMutation.mutate({ source: src() })} 331 + disabled={exportMutation.isPending} 332 + > 333 + {exportMutation.isPending ? 'Exporting...' : 'Export to file'} 334 + </Button> 335 + <Show when={getExportStatusText()}> 336 + {(text) => <span class="text-sm text-gray-600">{text()}</span>} 337 + </Show> 338 + </div> 339 + 340 + <Show when={exportMutation.error}> 341 + {(err) => <p class="text-sm text-red-600">{`${err()}`}</p>} 342 + </Show> 343 + </> 344 + )} 345 + </Show> 346 + </Subsection> 347 + 348 + <Subsection title="Import to destination"> 349 + <p class="text-sm text-gray-600">Upload blobs from a tarball or transfer directly from source.</p> 350 + 351 + <Show 352 + when={destination()?.manager} 353 + fallback={<p class="text-sm text-gray-500">Sign in to destination account first.</p>} 354 + > 355 + {(destManager) => ( 356 + <> 357 + <div class="flex flex-wrap items-center gap-3"> 358 + <Button 359 + onClick={() => importFromFileMutation.mutate({ destManager: destManager() })} 360 + disabled={isImporting()} 361 + > 362 + {isImporting() ? 'Importing...' : 'Import from file'} 363 + </Button> 364 + 365 + <Show when={source()}> 366 + {(src) => ( 367 + <Button 368 + variant="secondary" 369 + onClick={() => 370 + importFromSourceMutation.mutate({ source: src(), destManager: destManager() }) 371 + } 372 + disabled={isImporting()} 373 + > 374 + Transfer from source 375 + </Button> 376 + )} 377 + </Show> 378 + </div> 379 + 380 + <Show when={getImportStatusText()}> 381 + {(text) => <span class="text-sm text-gray-600">{text()}</span>} 382 + </Show> 383 + 384 + <Show when={getImportError()}>{(err) => <p class="text-sm text-red-600">{`${err()}`}</p>}</Show> 385 + </> 386 + )} 387 + </Show> 388 + </Subsection> 389 + 390 + <Subsection title="Status"> 391 + <Show 392 + when={destination()?.manager} 393 + fallback={<p class="text-sm text-gray-500">Sign in to destination account first.</p>} 394 + > 395 + {(destManager) => ( 396 + <> 397 + <div class="flex items-center gap-3"> 398 + <Button 399 + variant="outline" 400 + onClick={() => checkStatusMutation.mutate({ destManager: destManager() })} 401 + disabled={checkStatusMutation.isPending} 402 + > 403 + {checkStatusMutation.isPending ? 'Checking...' : 'Check status'} 404 + </Button> 405 + 406 + <Show when={checkStatusMutation.data}> 407 + {(status) => ( 408 + <span class="text-sm"> 409 + <StatusBadge variant={status().imported === status().expected ? 'success' : 'pending'}> 410 + {status().imported}/{status().expected} blobs 411 + </StatusBadge> 412 + </span> 413 + )} 414 + </Show> 415 + </div> 416 + 417 + <Show when={checkStatusMutation.data?.missingBlobs.length}> 418 + {(count) => ( 419 + <div class="mt-2 rounded border border-yellow-300 bg-yellow-50 p-3"> 420 + <p class="mb-2 text-sm font-medium text-yellow-800">{count()} missing blobs</p> 421 + 422 + <Show when={source()}> 423 + {(src) => ( 424 + <Button 425 + variant="secondary" 426 + onClick={() => 427 + importFromSourceMutation.mutate({ source: src(), destManager: destManager() }) 428 + } 429 + disabled={isImporting()} 430 + > 431 + Transfer missing from source 432 + </Button> 433 + )} 434 + </Show> 435 + 436 + <details class="mt-2"> 437 + <summary class="cursor-pointer text-sm text-yellow-700">Show CIDs</summary> 438 + <div class="mt-1 max-h-32 overflow-auto font-mono text-xs"> 439 + <For each={checkStatusMutation.data?.missingBlobs}> 440 + {(cid) => <div class="truncate">{cid}</div>} 441 + </For> 442 + </div> 443 + </details> 444 + </div> 445 + )} 446 + </Show> 447 + </> 448 + )} 449 + </Show> 450 + </Subsection> 451 + </Accordion> 452 + ); 453 + }; 454 + 455 + export default BlobsSection;
+437
src/views/account/account-migrate/sections/destination-account.tsx
··· 1 + import { createSignal, Show } from 'solid-js'; 2 + 3 + import { 4 + type AtpAccessJwt, 5 + Client, 6 + ClientResponseError, 7 + CredentialManager, 8 + ok, 9 + simpleFetchHandler, 10 + } from '@atcute/client'; 11 + import type { Did, Handle } from '@atcute/lexicons/syntax'; 12 + 13 + import { formatTotpCode, TOTP_RE } from '~/api/utils/auth'; 14 + import { decodeJwt } from '~/api/utils/jwt'; 15 + import { isServiceUrlString } from '~/api/types/strings'; 16 + 17 + import { createMutation } from '~/lib/utils/mutation'; 18 + 19 + import { Accordion, StatusBadge, Subsection } from '~/components/accordion'; 20 + import Button from '~/components/inputs/button'; 21 + import TextInput from '~/components/inputs/text-input'; 22 + 23 + import { useMigration } from '../context'; 24 + 25 + class InsufficientLoginError extends Error {} 26 + 27 + const DestinationAccountSection = () => { 28 + const { source, destination, setDestination } = useMigration(); 29 + 30 + // Connect state 31 + const [pdsUrl, setPdsUrl] = createSignal(''); 32 + const [connectError, setConnectError] = createSignal<string>(); 33 + 34 + // Create account state 35 + const [newHandle, setNewHandle] = createSignal(''); 36 + const [newEmail, setNewEmail] = createSignal(''); 37 + const [newPassword, setNewPassword] = createSignal(''); 38 + const [inviteCode, setInviteCode] = createSignal(''); 39 + const [createError, setCreateError] = createSignal<string>(); 40 + 41 + // Login state 42 + const [loginPassword, setLoginPassword] = createSignal(''); 43 + const [loginOtp, setLoginOtp] = createSignal(''); 44 + const [isLoginTotpRequired, setIsLoginTotpRequired] = createSignal(false); 45 + const [loginError, setLoginError] = createSignal<string>(); 46 + 47 + const connectMutation = createMutation({ 48 + async mutationFn({ pdsUrl }: { pdsUrl: string }) { 49 + const destClient = new Client({ handler: simpleFetchHandler({ service: pdsUrl }) }); 50 + const desc = await ok(destClient.get('com.atproto.server.describeServer')); 51 + 52 + return { serviceDid: desc.did }; 53 + }, 54 + onMutate() { 55 + setConnectError(); 56 + }, 57 + onSuccess({ serviceDid }) { 58 + setDestination({ pdsUrl: pdsUrl(), serviceDid, manager: null }); 59 + }, 60 + onError(err) { 61 + console.error(err); 62 + setConnectError(`Failed to connect: ${err}`); 63 + }, 64 + }); 65 + 66 + const createAccountMutation = createMutation({ 67 + async mutationFn({ 68 + sourceDid, 69 + sourceManager, 70 + destPdsUrl, 71 + destServiceDid, 72 + handle, 73 + email, 74 + password, 75 + inviteCode, 76 + }: { 77 + sourceDid: Did; 78 + sourceManager: CredentialManager; 79 + destPdsUrl: string; 80 + destServiceDid: string; 81 + handle: Handle; 82 + email: string; 83 + password: string; 84 + inviteCode: string; 85 + }) { 86 + // Get service auth token from old PDS 87 + const sourceClient = new Client({ handler: sourceManager }); 88 + const authResp = await ok( 89 + sourceClient.get('com.atproto.server.getServiceAuth', { 90 + params: { 91 + aud: destServiceDid as Did, 92 + lxm: 'com.atproto.server.createAccount', 93 + }, 94 + }), 95 + ); 96 + const serviceJwt = authResp.token; 97 + 98 + // Create account on new PDS with service auth 99 + const destClient = new Client({ handler: simpleFetchHandler({ service: destPdsUrl }) }); 100 + const createResp = await destClient.post('com.atproto.server.createAccount', { 101 + headers: { Authorization: `Bearer ${serviceJwt}` }, 102 + input: { 103 + did: sourceDid, 104 + handle: handle, 105 + email: email, 106 + password: password, 107 + inviteCode: inviteCode || undefined, 108 + }, 109 + }); 110 + 111 + if (!createResp.ok) { 112 + throw new ClientResponseError(createResp); 113 + } 114 + 115 + if (createResp.data.did !== sourceDid) { 116 + throw new Error(`Created account has different DID: ${createResp.data.did}`); 117 + } 118 + 119 + // Login to the new account 120 + const manager = new CredentialManager({ service: destPdsUrl }); 121 + await manager.login({ 122 + identifier: sourceDid, 123 + password: password, 124 + }); 125 + 126 + return manager; 127 + }, 128 + onMutate() { 129 + setCreateError(); 130 + }, 131 + onSuccess(manager) { 132 + setDestination({ ...destination()!, manager }); 133 + setNewPassword(''); 134 + }, 135 + onError(err) { 136 + if (err instanceof ClientResponseError) { 137 + if (err.error === 'InvalidInviteCode') { 138 + setCreateError(`Invalid invite code`); 139 + return; 140 + } 141 + if (err.error === 'HandleNotAvailable') { 142 + setCreateError(`Handle is not available`); 143 + return; 144 + } 145 + if (err.description) { 146 + setCreateError(err.description); 147 + return; 148 + } 149 + } 150 + console.error(err); 151 + setCreateError(`${err}`); 152 + }, 153 + }); 154 + 155 + const loginMutation = createMutation({ 156 + async mutationFn({ 157 + pdsUrl, 158 + did, 159 + password, 160 + otp, 161 + }: { 162 + pdsUrl: string; 163 + did: string; 164 + password: string; 165 + otp: string; 166 + }) { 167 + const manager = new CredentialManager({ service: pdsUrl }); 168 + const session = await manager.login({ 169 + identifier: did, 170 + password: password, 171 + code: formatTotpCode(otp), 172 + }); 173 + 174 + const decoded = decodeJwt(session.accessJwt) as AtpAccessJwt; 175 + if (decoded.scope !== 'com.atproto.access') { 176 + throw new InsufficientLoginError(`You need to sign in with a main password, not an app password`); 177 + } 178 + 179 + return manager; 180 + }, 181 + onMutate() { 182 + setLoginError(); 183 + }, 184 + onSuccess(manager) { 185 + setDestination({ ...destination()!, manager }); 186 + setLoginPassword(''); 187 + setLoginOtp(''); 188 + setIsLoginTotpRequired(false); 189 + }, 190 + onError(err) { 191 + if (err instanceof ClientResponseError) { 192 + if (err.error === 'AuthFactorTokenRequired') { 193 + setLoginOtp(''); 194 + setIsLoginTotpRequired(true); 195 + return; 196 + } 197 + if (err.error === 'AuthenticationRequired') { 198 + setLoginError(`Invalid identifier or password`); 199 + return; 200 + } 201 + if (err.description?.includes('Token is invalid')) { 202 + setLoginError(`Invalid one-time confirmation code`); 203 + setIsLoginTotpRequired(true); 204 + return; 205 + } 206 + } 207 + if (err instanceof InsufficientLoginError) { 208 + setLoginError(err.message); 209 + return; 210 + } 211 + console.error(err); 212 + setLoginError(`${err}`); 213 + }, 214 + }); 215 + 216 + const isConnected = () => destination() !== null; 217 + const isAuthenticated = () => destination()?.manager != null; 218 + const canCreateAccount = () => source()?.manager != null; 219 + 220 + return ( 221 + <Accordion title="Destination Account"> 222 + <Subsection title="Connect to PDS"> 223 + <Show when={!isConnected()}> 224 + <form 225 + onSubmit={(ev) => { 226 + ev.preventDefault(); 227 + connectMutation.mutate({ pdsUrl: pdsUrl() }); 228 + }} 229 + class="flex flex-col gap-3" 230 + > 231 + <TextInput 232 + label="PDS URL" 233 + type="url" 234 + placeholder="https://pds.example.com" 235 + value={pdsUrl()} 236 + required 237 + onChange={(text, event) => { 238 + setPdsUrl(text); 239 + const input = event.currentTarget; 240 + if (text !== '' && !isServiceUrlString(text)) { 241 + input.setCustomValidity('Must be a valid URL'); 242 + } else { 243 + input.setCustomValidity(''); 244 + } 245 + }} 246 + /> 247 + 248 + <Show when={connectError()}> 249 + <p class="text-sm text-red-600">{connectError()}</p> 250 + </Show> 251 + 252 + <div> 253 + <Button type="submit" disabled={connectMutation.isPending}> 254 + {connectMutation.isPending ? 'Connecting...' : 'Connect'} 255 + </Button> 256 + </div> 257 + </form> 258 + </Show> 259 + 260 + <Show when={isConnected()}> 261 + <div class="flex flex-col gap-2 text-sm"> 262 + <p> 263 + <span class="text-gray-500">URL:</span>{' '} 264 + <span class="font-mono">{destination()!.pdsUrl}</span> 265 + </p> 266 + <p> 267 + <span class="text-gray-500">Service DID:</span>{' '} 268 + <span class="font-mono">{destination()!.serviceDid}</span> 269 + </p> 270 + <div class="mt-1"> 271 + <button 272 + type="button" 273 + onClick={() => setDestination(null)} 274 + class="text-sm text-purple-800 hover:underline" 275 + > 276 + Change PDS 277 + </button> 278 + </div> 279 + </div> 280 + </Show> 281 + </Subsection> 282 + 283 + <Show when={isConnected() && !isAuthenticated()}> 284 + <Subsection title="Create new account"> 285 + <Show when={!canCreateAccount()}> 286 + <p class="text-sm text-gray-600"> 287 + You need to authenticate to your source account first to create an account on the 288 + destination PDS. 289 + </p> 290 + </Show> 291 + 292 + <Show when={canCreateAccount()}> 293 + <form 294 + onSubmit={(ev) => { 295 + ev.preventDefault(); 296 + const src = source()!; 297 + const dest = destination()!; 298 + createAccountMutation.mutate({ 299 + sourceDid: src.did, 300 + sourceManager: src.manager!, 301 + destPdsUrl: dest.pdsUrl, 302 + destServiceDid: dest.serviceDid, 303 + handle: newHandle() as Handle, 304 + email: newEmail(), 305 + password: newPassword(), 306 + inviteCode: inviteCode(), 307 + }); 308 + }} 309 + class="flex flex-col gap-3" 310 + > 311 + <TextInput 312 + label="Handle" 313 + placeholder="alice.pds.example.com" 314 + value={newHandle()} 315 + required 316 + onChange={setNewHandle} 317 + /> 318 + 319 + <TextInput 320 + label="Email" 321 + type="email" 322 + placeholder="alice@example.com" 323 + value={newEmail()} 324 + required 325 + onChange={setNewEmail} 326 + /> 327 + 328 + <TextInput 329 + label="Password" 330 + type="password" 331 + value={newPassword()} 332 + required 333 + onChange={setNewPassword} 334 + /> 335 + 336 + <TextInput 337 + label="Invite code (if required)" 338 + placeholder="pds-example-com-xxxxx" 339 + value={inviteCode()} 340 + onChange={setInviteCode} 341 + /> 342 + 343 + <Show when={createError()}> 344 + <p class="text-sm text-red-600">{createError()}</p> 345 + </Show> 346 + 347 + <div> 348 + <Button type="submit" disabled={createAccountMutation.isPending}> 349 + {createAccountMutation.isPending ? 'Creating...' : 'Create account'} 350 + </Button> 351 + </div> 352 + </form> 353 + </Show> 354 + </Subsection> 355 + 356 + <Subsection title="Or login to existing account"> 357 + <p class="mb-2 text-sm text-gray-600"> 358 + If you already have a deactivated account on the destination PDS. 359 + </p> 360 + 361 + <Show when={!source()}> 362 + <p class="text-sm text-gray-600"> 363 + Resolve your source account first so we know which DID to use. 364 + </p> 365 + </Show> 366 + 367 + <Show when={source()}> 368 + <form 369 + onSubmit={(ev) => { 370 + ev.preventDefault(); 371 + const src = source()!; 372 + const dest = destination()!; 373 + loginMutation.mutate({ 374 + pdsUrl: dest.pdsUrl, 375 + did: src.did, 376 + password: loginPassword(), 377 + otp: loginOtp(), 378 + }); 379 + }} 380 + class="flex flex-col gap-3" 381 + > 382 + <TextInput 383 + label="Password" 384 + type="password" 385 + value={loginPassword()} 386 + required 387 + onChange={setLoginPassword} 388 + /> 389 + 390 + <Show when={isLoginTotpRequired()}> 391 + <TextInput 392 + label="One-time confirmation code" 393 + blurb="A code has been sent to your email address." 394 + type="text" 395 + autocomplete="one-time-code" 396 + pattern={TOTP_RE.source} 397 + placeholder="AAAAA-BBBBB" 398 + value={loginOtp()} 399 + required 400 + onChange={setLoginOtp} 401 + monospace 402 + /> 403 + </Show> 404 + 405 + <Show when={loginError()}> 406 + <p class="text-sm text-red-600">{loginError()}</p> 407 + </Show> 408 + 409 + <div> 410 + <Button type="submit" disabled={loginMutation.isPending}> 411 + {loginMutation.isPending ? 'Signing in...' : 'Sign in'} 412 + </Button> 413 + </div> 414 + </form> 415 + </Show> 416 + </Subsection> 417 + </Show> 418 + 419 + <Show when={isAuthenticated()}> 420 + <Subsection title="Account status"> 421 + <div class="flex items-center gap-2"> 422 + <StatusBadge variant="success">Signed in</StatusBadge> 423 + <button 424 + type="button" 425 + onClick={() => setDestination({ ...destination()!, manager: null })} 426 + class="text-sm text-purple-800 hover:underline" 427 + > 428 + Sign out 429 + </button> 430 + </div> 431 + </Subsection> 432 + </Show> 433 + </Accordion> 434 + ); 435 + }; 436 + 437 + export default DestinationAccountSection;
+545
src/views/account/account-migrate/sections/identity.tsx
··· 1 + import { createSignal, For, Index, Show } from 'solid-js'; 2 + 3 + import { Client, ClientResponseError, type CredentialManager, ok } from '@atcute/client'; 4 + import { type DidKeyString, Secp256k1PrivateKeyExportable } from '@atcute/crypto'; 5 + import type { Did } from '@atcute/lexicons/syntax'; 6 + 7 + import { getPlcAuditLogs } from '~/api/queries/plc'; 8 + import { formatTotpCode, TOTP_RE } from '~/api/utils/auth'; 9 + 10 + import { createMutation } from '~/lib/utils/mutation'; 11 + 12 + import { Accordion, StatusBadge, Subsection } from '~/components/accordion'; 13 + import Button from '~/components/inputs/button'; 14 + import TextInput from '~/components/inputs/text-input'; 15 + import ToggleInput from '~/components/inputs/toggle-input'; 16 + 17 + import { getPlcPayload } from '~/views/identity/plc-applicator/plc-utils'; 18 + 19 + import { useMigration } from '../context'; 20 + 21 + interface RecommendedCredentials { 22 + alsoKnownAs?: string[]; 23 + rotationKeys?: string[]; 24 + verificationMethods?: Record<string, unknown>; 25 + services?: Record<string, unknown>; 26 + } 27 + 28 + interface GeneratedKeypair { 29 + publicDidKey: DidKeyString; 30 + privateHex: string; 31 + privateMultikey: string; 32 + } 33 + 34 + const IdentitySection = () => { 35 + const { source, destination } = useMigration(); 36 + 37 + // Rotation key state 38 + const [useGeneratedKey, setUseGeneratedKey] = createSignal(false); 39 + const [customKeys, setCustomKeys] = createSignal<string[]>([]); 40 + const [plcToken, setPlcToken] = createSignal(''); 41 + 42 + const requestTokenMutation = createMutation({ 43 + async mutationFn({ manager }: { manager: CredentialManager }) { 44 + const client = new Client({ handler: manager }); 45 + await ok(client.post('com.atproto.identity.requestPlcOperationSignature', { as: null })); 46 + }, 47 + onError(err) { 48 + console.error(err); 49 + }, 50 + }); 51 + 52 + const loadCredentialsMutation = createMutation({ 53 + async mutationFn({ manager }: { manager: CredentialManager }) { 54 + const client = new Client({ handler: manager }); 55 + return (await ok( 56 + client.get('com.atproto.identity.getRecommendedDidCredentials', {}), 57 + )) as RecommendedCredentials; 58 + }, 59 + onError(err) { 60 + console.error(err); 61 + }, 62 + }); 63 + 64 + // Analyze current rotation keys to find user-controlled keys that should be preserved 65 + const analyzeRotationKeysMutation = createMutation({ 66 + async mutationFn({ did, sourceManager }: { did: Did<'plc'>; sourceManager: CredentialManager }, signal) { 67 + // Get current rotation keys from PLC audit log 68 + const auditLogs = await getPlcAuditLogs({ did, signal }); 69 + const latestEntry = auditLogs[auditLogs.length - 1]; 70 + const currentPayload = getPlcPayload(latestEntry); 71 + const currentRotationKeys = currentPayload.rotationKeys ?? []; 72 + 73 + // Get source PDS's recommended credentials to identify PDS-controlled keys 74 + const sourceClient = new Client({ handler: sourceManager }); 75 + const sourcePdsCredentials = (await ok( 76 + sourceClient.get('com.atproto.identity.getRecommendedDidCredentials', {}), 77 + )) as RecommendedCredentials; 78 + const sourcePdsKeys = new Set(sourcePdsCredentials.rotationKeys ?? []); 79 + 80 + // Keys in current doc that aren't from source PDS are user-controlled 81 + const userControlledKeys = currentRotationKeys.filter((key) => !sourcePdsKeys.has(key)); 82 + 83 + return { 84 + currentRotationKeys, 85 + sourcePdsKeys: sourcePdsCredentials.rotationKeys ?? [], 86 + userControlledKeys, 87 + }; 88 + }, 89 + onSuccess(data) { 90 + // Pre-populate custom keys with user-controlled keys 91 + if (data.userControlledKeys.length > 0) { 92 + setCustomKeys(data.userControlledKeys); 93 + } 94 + }, 95 + onError(err) { 96 + console.error(err); 97 + }, 98 + }); 99 + 100 + const generateKeyMutation = createMutation({ 101 + async mutationFn() { 102 + const keypair = await Secp256k1PrivateKeyExportable.createKeypair(); 103 + const [publicDidKey, privateHex, privateMultikey] = await Promise.all([ 104 + keypair.exportPublicKey('did'), 105 + keypair.exportPrivateKey('rawHex'), 106 + keypair.exportPrivateKey('multikey'), 107 + ]); 108 + return { publicDidKey, privateHex, privateMultikey } as GeneratedKeypair; 109 + }, 110 + onError(err) { 111 + console.error(err); 112 + }, 113 + }); 114 + 115 + const signAndSubmitMutation = createMutation({ 116 + async mutationFn({ 117 + sourceManager, 118 + destManager, 119 + token, 120 + credentials, 121 + generatedKey, 122 + customKeys, 123 + }: { 124 + sourceManager: CredentialManager; 125 + destManager: CredentialManager; 126 + token: string; 127 + credentials: RecommendedCredentials; 128 + generatedKey?: GeneratedKeypair; 129 + customKeys: string[]; 130 + }) { 131 + const sourceClient = new Client({ handler: sourceManager }); 132 + const destClient = new Client({ handler: destManager }); 133 + 134 + // Prepend user keys to PDS-provided keys (so user keys appear first for recovery) 135 + const pdsRotationKeys = credentials.rotationKeys ?? []; 136 + const userKeys: string[] = []; 137 + if (generatedKey) { 138 + userKeys.push(generatedKey.publicDidKey); 139 + } 140 + userKeys.push(...customKeys.filter((k) => k.trim())); 141 + const rotationKeys = [...userKeys, ...pdsRotationKeys]; 142 + 143 + // Sign the PLC operation on the source PDS 144 + const signage = await ok( 145 + sourceClient.post('com.atproto.identity.signPlcOperation', { 146 + input: { 147 + token: formatTotpCode(token), 148 + alsoKnownAs: credentials.alsoKnownAs, 149 + rotationKeys: rotationKeys, 150 + services: credentials.services, 151 + verificationMethods: credentials.verificationMethods, 152 + }, 153 + }), 154 + ); 155 + 156 + // Submit via the destination PDS 157 + await ok( 158 + destClient.post('com.atproto.identity.submitPlcOperation', { 159 + as: null, 160 + input: { 161 + operation: signage.operation, 162 + }, 163 + }), 164 + ); 165 + }, 166 + onSuccess() { 167 + setPlcToken(''); 168 + }, 169 + onError(err) { 170 + console.error(err); 171 + }, 172 + }); 173 + 174 + // Calculate rotation key counts 175 + const pdsKeyCount = () => loadCredentialsMutation.data?.rotationKeys?.length ?? 0; 176 + const totalKeyCount = () => { 177 + const custom = customKeys().filter((k) => k.trim()).length; 178 + const generated = useGeneratedKey() && generateKeyMutation.data ? 1 : 0; 179 + return pdsKeyCount() + custom + generated; 180 + }; 181 + const canAddCustomKey = () => totalKeyCount() < 5; 182 + const isOverLimit = () => totalKeyCount() > 5; 183 + 184 + const addCustomKey = () => { 185 + if (canAddCustomKey()) { 186 + setCustomKeys([...customKeys(), '']); 187 + } 188 + }; 189 + 190 + const removeCustomKey = (index: number) => { 191 + setCustomKeys(customKeys().filter((_, i) => i !== index)); 192 + }; 193 + 194 + const updateCustomKey = (index: number, value: string) => { 195 + setCustomKeys(customKeys().map((k, i) => (i === index ? value : k))); 196 + }; 197 + 198 + const canSignAndSubmit = () => { 199 + const src = source(); 200 + const dest = destination(); 201 + const creds = loadCredentialsMutation.data; 202 + const token = plcToken().trim(); 203 + 204 + return !!(src?.manager && dest?.manager && creds && token && !isOverLimit()); 205 + }; 206 + 207 + const handleSignAndSubmit = () => { 208 + const src = source(); 209 + const dest = destination(); 210 + const creds = loadCredentialsMutation.data; 211 + const token = plcToken().trim(); 212 + 213 + if (!src?.manager || !dest?.manager || !creds || !token || isOverLimit()) return; 214 + 215 + signAndSubmitMutation.mutate({ 216 + sourceManager: src.manager, 217 + destManager: dest.manager, 218 + token, 219 + credentials: creds, 220 + generatedKey: useGeneratedKey() ? generateKeyMutation.data : undefined, 221 + customKeys: customKeys(), 222 + }); 223 + }; 224 + 225 + const getSubmitErrorMessage = () => { 226 + const err = signAndSubmitMutation.error; 227 + if (err instanceof ClientResponseError) { 228 + if (err.error === 'InvalidToken' || err.error === 'ExpiredToken') { 229 + return 'Confirmation code has expired or is invalid'; 230 + } 231 + } 232 + return `${err}`; 233 + }; 234 + 235 + return ( 236 + <Accordion title="Identity (PLC)"> 237 + <div class="mb-4 rounded border border-yellow-300 bg-yellow-50 p-3"> 238 + <p class="text-sm font-medium text-yellow-800"> 239 + This updates your DID document to point to the new PDS. This is the critical step that makes the 240 + migration official. 241 + </p> 242 + </div> 243 + 244 + <Subsection title="1. Preview new credentials"> 245 + <p class="text-sm text-gray-600">View what your DID document will look like after the migration.</p> 246 + 247 + <Show 248 + when={destination()?.manager} 249 + fallback={<p class="text-sm text-gray-500">Sign in to destination account first.</p>} 250 + > 251 + {(manager) => ( 252 + <> 253 + <div class="flex items-center gap-3"> 254 + <Button 255 + variant="outline" 256 + onClick={() => loadCredentialsMutation.mutate({ manager: manager() })} 257 + disabled={loadCredentialsMutation.isPending} 258 + > 259 + {loadCredentialsMutation.isPending ? 'Loading...' : 'Load credentials'} 260 + </Button> 261 + 262 + <Show when={loadCredentialsMutation.isSuccess}> 263 + <StatusBadge variant="success">Loaded</StatusBadge> 264 + </Show> 265 + </div> 266 + 267 + <Show when={loadCredentialsMutation.isError}> 268 + <p class="text-sm text-red-600">{`${loadCredentialsMutation.error}`}</p> 269 + </Show> 270 + 271 + <Show when={loadCredentialsMutation.data}> 272 + {(creds) => ( 273 + <> 274 + <div class="mt-2 text-sm"> 275 + <p class="text-gray-500"> 276 + Destination PDS rotation keys ({creds().rotationKeys?.length ?? 0}/5): 277 + </p> 278 + <div class="mt-1 flex flex-col gap-1"> 279 + <For each={creds().rotationKeys ?? []}> 280 + {(key) => <code class="block truncate text-xs text-gray-700">{key}</code>} 281 + </For> 282 + </div> 283 + </div> 284 + 285 + <Show when={source()?.manager && source()}> 286 + {(src) => ( 287 + <div class="mt-3 rounded border border-blue-200 bg-blue-50 p-3"> 288 + <div class="flex items-center justify-between"> 289 + <p class="text-sm font-medium text-blue-800">Analyze existing rotation keys</p> 290 + <Button 291 + variant="outline" 292 + onClick={() => 293 + analyzeRotationKeysMutation.mutate({ 294 + did: src().did as Did<'plc'>, 295 + sourceManager: src().manager!, 296 + }) 297 + } 298 + disabled={analyzeRotationKeysMutation.isPending} 299 + > 300 + {analyzeRotationKeysMutation.isPending ? 'Analyzing...' : 'Analyze'} 301 + </Button> 302 + </div> 303 + <p class="mt-1 text-xs text-blue-600"> 304 + Check if you have any user-controlled rotation keys that should be preserved 305 + during migration. 306 + </p> 307 + 308 + <Show when={analyzeRotationKeysMutation.error}> 309 + <p class="mt-2 text-sm text-red-600">{`${analyzeRotationKeysMutation.error}`}</p> 310 + </Show> 311 + 312 + <Show when={analyzeRotationKeysMutation.data}> 313 + {(analysis) => ( 314 + <div class="mt-2 text-sm"> 315 + <Show 316 + when={analysis().userControlledKeys.length > 0} 317 + fallback={ 318 + <p class="text-blue-700"> 319 + No user-controlled rotation keys found. Your current keys are all 320 + managed by your source PDS. 321 + </p> 322 + } 323 + > 324 + <p class="font-medium text-blue-800"> 325 + Found {analysis().userControlledKeys.length} user-controlled key(s) to 326 + preserve: 327 + </p> 328 + <div class="mt-1 flex flex-col gap-1"> 329 + <For each={analysis().userControlledKeys}> 330 + {(key) => ( 331 + <code class="block truncate text-xs text-blue-700">{key}</code> 332 + )} 333 + </For> 334 + </div> 335 + <p class="mt-2 text-xs text-blue-600"> 336 + These keys have been added to the custom keys section below. 337 + </p> 338 + </Show> 339 + </div> 340 + )} 341 + </Show> 342 + </div> 343 + )} 344 + </Show> 345 + 346 + <details class="mt-2"> 347 + <summary class="cursor-pointer text-sm text-gray-600">View full credentials</summary> 348 + <pre class="mt-2 max-h-48 overflow-auto rounded border border-gray-200 bg-gray-50 p-2 font-mono text-xs"> 349 + {JSON.stringify(creds(), null, 2)} 350 + </pre> 351 + </details> 352 + </> 353 + )} 354 + </Show> 355 + </> 356 + )} 357 + </Show> 358 + </Subsection> 359 + 360 + <Subsection title="2. Rotation keys (optional)"> 361 + <p class="text-sm text-gray-600"> 362 + Add a rotation key to recover your account if your new PDS goes rogue. This will be prepended to the 363 + PDS rotation keys shown above. 364 + </p> 365 + 366 + <ToggleInput 367 + label="Generate a new rotation key" 368 + checked={useGeneratedKey()} 369 + onChange={(checked) => { 370 + setUseGeneratedKey(checked); 371 + // Auto-generate if checked and no key exists yet 372 + if (checked && !generateKeyMutation.data && !generateKeyMutation.isPending) { 373 + generateKeyMutation.mutate(); 374 + } 375 + }} 376 + /> 377 + 378 + <Show when={useGeneratedKey() && generateKeyMutation.isPending}> 379 + <p class="mt-2 text-sm text-gray-500">Generating key...</p> 380 + </Show> 381 + 382 + <Show when={useGeneratedKey() && generateKeyMutation.isError}> 383 + <p class="mt-2 text-sm text-red-600">{`${generateKeyMutation.error}`}</p> 384 + </Show> 385 + 386 + <Show when={useGeneratedKey() && generateKeyMutation.data}> 387 + {(keypair) => ( 388 + <div class="rounded border border-green-300 bg-green-50 p-3"> 389 + <p class="mb-2 text-sm font-semibold text-green-800">Save your rotation key private key!</p> 390 + <p class="mb-3 text-xs text-green-700"> 391 + Store this securely. You'll need it to recover your account if your PDS becomes unavailable or 392 + malicious. 393 + </p> 394 + 395 + <div class="flex flex-col gap-2 text-sm"> 396 + <div> 397 + <p class="font-medium text-gray-600">Public key (did:key)</p> 398 + <p class="break-all font-mono text-xs">{keypair().publicDidKey}</p> 399 + </div> 400 + <div> 401 + <p class="font-medium text-gray-600">Private key (hex)</p> 402 + <p class="break-all font-mono text-xs">{keypair().privateHex}</p> 403 + </div> 404 + <div> 405 + <p class="font-medium text-gray-600">Private key (multikey)</p> 406 + <p class="break-all font-mono text-xs">{keypair().privateMultikey}</p> 407 + </div> 408 + </div> 409 + </div> 410 + )} 411 + </Show> 412 + 413 + <div class="rounded border border-gray-200 bg-gray-50 p-3"> 414 + <p class="mb-2 text-sm font-medium text-gray-700">Custom rotation keys</p> 415 + <p class="mb-3 text-xs text-gray-500"> 416 + Add existing rotation keys (did:key format) you already control. 417 + </p> 418 + 419 + <Index each={customKeys()}> 420 + {(key, index) => ( 421 + <div class="mb-2 flex items-center gap-2"> 422 + <TextInput 423 + label="" 424 + placeholder="did:key:z..." 425 + monospace 426 + autocomplete="off" 427 + value={key()} 428 + onChange={(value) => updateCustomKey(index, value)} 429 + /> 430 + <button 431 + type="button" 432 + class="shrink-0 rounded px-2 py-1 text-sm text-red-600 hover:bg-red-50" 433 + onClick={() => removeCustomKey(index)} 434 + > 435 + Remove 436 + </button> 437 + </div> 438 + )} 439 + </Index> 440 + 441 + <Button variant="outline" onClick={addCustomKey} disabled={!canAddCustomKey()}> 442 + Add rotation key 443 + </Button> 444 + 445 + <Show when={isOverLimit()}> 446 + <p class="mt-2 text-sm text-red-600"> 447 + Too many rotation keys. PLC documents can only have up to 5 rotation keys total. 448 + </p> 449 + </Show> 450 + 451 + <p class="mt-2 text-xs text-gray-500"> 452 + Total keys: {totalKeyCount()}/5 (PDS: {pdsKeyCount()} 453 + {useGeneratedKey() && generateKeyMutation.data ? ', generated: 1' : ''} 454 + {customKeys().filter((k) => k.trim()).length > 0 455 + ? `, custom: ${customKeys().filter((k) => k.trim()).length}` 456 + : ''} 457 + ) 458 + </p> 459 + </div> 460 + </Subsection> 461 + 462 + <Subsection title="3. Request operation signature"> 463 + <p class="text-sm text-gray-600">Request a confirmation token via email from your source PDS.</p> 464 + 465 + <Show 466 + when={source()?.manager} 467 + fallback={<p class="text-sm text-gray-500">Sign in to source account first.</p>} 468 + > 469 + {(manager) => ( 470 + <> 471 + <div class="flex items-center gap-3"> 472 + <Button 473 + onClick={() => requestTokenMutation.mutate({ manager: manager() })} 474 + disabled={requestTokenMutation.isPending} 475 + > 476 + {requestTokenMutation.isPending ? 'Requesting...' : 'Request token'} 477 + </Button> 478 + 479 + <Show when={requestTokenMutation.isSuccess}> 480 + <StatusBadge variant="success">Email sent</StatusBadge> 481 + </Show> 482 + </div> 483 + 484 + <Show when={requestTokenMutation.isError}> 485 + <p class="text-sm text-red-600">{`${requestTokenMutation.error}`}</p> 486 + </Show> 487 + 488 + <Show when={requestTokenMutation.isSuccess}> 489 + <p class="text-sm text-gray-600">Check your email inbox for the confirmation code.</p> 490 + </Show> 491 + </> 492 + )} 493 + </Show> 494 + </Subsection> 495 + 496 + <Subsection title="4. Sign and submit"> 497 + <p class="text-sm text-gray-600">Enter the confirmation code and submit the PLC operation.</p> 498 + 499 + <Show when={!source()?.manager || !destination()?.manager}> 500 + <p class="text-sm text-gray-500">Sign in to both source and destination accounts first.</p> 501 + </Show> 502 + 503 + <Show when={!loadCredentialsMutation.data}> 504 + <p class="text-sm text-gray-500">Load credentials first.</p> 505 + </Show> 506 + 507 + <Show when={useGeneratedKey() && !generateKeyMutation.data}> 508 + <p class="text-sm text-gray-500">Generate your rotation key first.</p> 509 + </Show> 510 + 511 + <Show when={source()?.manager && destination()?.manager && loadCredentialsMutation.data}> 512 + <TextInput 513 + label="Confirmation code from email" 514 + type="text" 515 + autocomplete="one-time-code" 516 + pattern={TOTP_RE.source} 517 + placeholder="AAAAA-BBBBB" 518 + value={plcToken()} 519 + onChange={setPlcToken} 520 + monospace 521 + /> 522 + 523 + <div class="flex items-center gap-3"> 524 + <Button 525 + onClick={handleSignAndSubmit} 526 + disabled={signAndSubmitMutation.isPending || !canSignAndSubmit()} 527 + > 528 + {signAndSubmitMutation.isPending ? 'Submitting...' : 'Sign and submit'} 529 + </Button> 530 + 531 + <Show when={signAndSubmitMutation.isSuccess}> 532 + <StatusBadge variant="success">Identity updated successfully</StatusBadge> 533 + </Show> 534 + </div> 535 + 536 + <Show when={signAndSubmitMutation.isError}> 537 + <p class="text-sm text-red-600">{getSubmitErrorMessage()}</p> 538 + </Show> 539 + </Show> 540 + </Subsection> 541 + </Accordion> 542 + ); 543 + }; 544 + 545 + export default IdentitySection;
+180
src/views/account/account-migrate/sections/preferences.tsx
··· 1 + import { showSaveFilePicker } from 'native-file-system-adapter'; 2 + import { createSignal, Show } from 'solid-js'; 3 + 4 + import { Client, type CredentialManager, ok } from '@atcute/client'; 5 + 6 + import { createMutation } from '~/lib/utils/mutation'; 7 + 8 + import { Accordion, StatusBadge, Subsection } from '~/components/accordion'; 9 + import Button from '~/components/inputs/button'; 10 + import MultilineInput from '~/components/inputs/multiline-input'; 11 + 12 + import { useMigration } from '../context'; 13 + 14 + const PreferencesSection = () => { 15 + const { source, destination } = useMigration(); 16 + 17 + const [prefsInput, setPrefsInput] = createSignal(''); 18 + 19 + const exportMutation = createMutation({ 20 + async mutationFn({ sourceManager }: { sourceManager: CredentialManager }) { 21 + const sourceClient = new Client({ handler: sourceManager }); 22 + const prefs = await ok(sourceClient.get('app.bsky.actor.getPreferences', { params: {} })); 23 + return JSON.stringify(prefs, null, 2); 24 + }, 25 + onSuccess(json) { 26 + setPrefsInput(json); 27 + }, 28 + onError(err) { 29 + console.error(err); 30 + }, 31 + }); 32 + 33 + const downloadPrefs = async () => { 34 + const prefs = exportMutation.data; 35 + if (!prefs) return; 36 + 37 + try { 38 + const fd = await showSaveFilePicker({ 39 + suggestedName: `preferences-${source()?.did}-${new Date().toISOString()}.json`, 40 + // @ts-expect-error: ponyfill doesn't have the full typings 41 + id: 'prefs-export', 42 + startIn: 'downloads', 43 + types: [ 44 + { 45 + description: 'JSON file', 46 + accept: { 'application/json': ['.json'] }, 47 + }, 48 + ], 49 + }).catch((err) => { 50 + if (err instanceof DOMException && err.name === 'AbortError') { 51 + return undefined; 52 + } 53 + throw err; 54 + }); 55 + 56 + if (!fd) return; 57 + 58 + const writable = await fd.createWritable(); 59 + await writable.write(prefs); 60 + await writable.close(); 61 + } catch (err) { 62 + console.error(err); 63 + } 64 + }; 65 + 66 + const importMutation = createMutation({ 67 + async mutationFn({ destManager, input }: { destManager: CredentialManager; input: string }) { 68 + const prefs = JSON.parse(input); 69 + 70 + // Validate that it has a preferences array 71 + if (!prefs.preferences || !Array.isArray(prefs.preferences)) { 72 + throw new Error('Invalid preferences format: missing preferences array'); 73 + } 74 + 75 + const destClient = new Client({ handler: destManager }); 76 + await destClient.post('app.bsky.actor.putPreferences', { 77 + as: null, 78 + input: prefs, 79 + }); 80 + }, 81 + onError(err) { 82 + console.error(err); 83 + }, 84 + }); 85 + 86 + const getImportErrorMessage = () => { 87 + const err = importMutation.error; 88 + if (err instanceof SyntaxError) { 89 + return 'Invalid JSON format'; 90 + } 91 + return `${err}`; 92 + }; 93 + 94 + return ( 95 + <Accordion title="Preferences"> 96 + <Subsection title="Export from source"> 97 + <p class="text-sm text-gray-600"> 98 + Export your Bluesky preferences (muted words, content filters, saved feeds, etc). 99 + </p> 100 + 101 + <Show 102 + when={source()?.manager} 103 + fallback={<p class="text-sm text-gray-500">Sign in to source account first.</p>} 104 + > 105 + {(sourceManager) => ( 106 + <> 107 + <div class="flex items-center gap-3"> 108 + <Button 109 + onClick={() => exportMutation.mutate({ sourceManager: sourceManager() })} 110 + disabled={exportMutation.isPending} 111 + > 112 + {exportMutation.isPending ? 'Exporting...' : 'Export preferences'} 113 + </Button> 114 + 115 + <Show when={exportMutation.data}> 116 + <Button variant="secondary" onClick={downloadPrefs}> 117 + Download as file 118 + </Button> 119 + </Show> 120 + </div> 121 + 122 + <Show when={exportMutation.error}> 123 + {(err) => <p class="text-sm text-red-600">{`${err()}`}</p>} 124 + </Show> 125 + 126 + <Show when={exportMutation.data}> 127 + {(prefs) => ( 128 + <details class="mt-2"> 129 + <summary class="cursor-pointer text-sm text-gray-600"> 130 + View exported preferences 131 + </summary> 132 + <pre class="mt-2 max-h-48 overflow-auto rounded border border-gray-200 bg-gray-50 p-2 font-mono text-xs"> 133 + {prefs()} 134 + </pre> 135 + </details> 136 + )} 137 + </Show> 138 + </> 139 + )} 140 + </Show> 141 + </Subsection> 142 + 143 + <Subsection title="Import to destination"> 144 + <p class="text-sm text-gray-600">Paste preferences JSON or use the exported data above.</p> 145 + 146 + <Show 147 + when={destination()?.manager} 148 + fallback={<p class="text-sm text-gray-500">Sign in to destination account first.</p>} 149 + > 150 + {(destManager) => ( 151 + <> 152 + <MultilineInput label="Preferences JSON" value={prefsInput()} onChange={setPrefsInput} /> 153 + 154 + <div class="flex items-center gap-3"> 155 + <Button 156 + onClick={() => 157 + importMutation.mutate({ destManager: destManager(), input: prefsInput().trim() }) 158 + } 159 + disabled={importMutation.isPending || !prefsInput().trim()} 160 + > 161 + {importMutation.isPending ? 'Importing...' : 'Import preferences'} 162 + </Button> 163 + 164 + <Show when={importMutation.isSuccess}> 165 + <StatusBadge variant="success">Preferences imported successfully</StatusBadge> 166 + </Show> 167 + </div> 168 + 169 + <Show when={importMutation.error}> 170 + <p class="text-sm text-red-600">{getImportErrorMessage()}</p> 171 + </Show> 172 + </> 173 + )} 174 + </Show> 175 + </Subsection> 176 + </Accordion> 177 + ); 178 + }; 179 + 180 + export default PreferencesSection;
+291
src/views/account/account-migrate/sections/repository.tsx
··· 1 + import { showOpenFilePicker, showSaveFilePicker } from 'native-file-system-adapter'; 2 + import { createSignal, Show } from 'solid-js'; 3 + 4 + import { Client, type CredentialManager, ok, simpleFetchHandler } from '@atcute/client'; 5 + import type { Did } from '@atcute/lexicons/syntax'; 6 + 7 + import { formatBytes } from '~/lib/utils/intl/bytes'; 8 + import { createMutation } from '~/lib/utils/mutation'; 9 + import { iterateStream } from '~/lib/utils/stream'; 10 + 11 + import { Accordion, StatusBadge, Subsection } from '~/components/accordion'; 12 + import Button from '~/components/inputs/button'; 13 + 14 + import { useMigration } from '../context'; 15 + 16 + const RepositorySection = () => { 17 + const { source, destination } = useMigration(); 18 + 19 + // Export state 20 + const [exportStatus, setExportStatus] = createSignal<string>(); 21 + 22 + // Import state 23 + const [importStatus, setImportStatus] = createSignal<string>(); 24 + const [importedRecords, setImportedRecords] = createSignal<number>(); 25 + 26 + const exportMutation = createMutation({ 27 + async mutationFn({ pdsUrl, did }: { pdsUrl: string; did: Did }) { 28 + setExportStatus('Waiting for file picker...'); 29 + 30 + const fd = await showSaveFilePicker({ 31 + suggestedName: `repo-${did}-${new Date().toISOString()}.car`, 32 + // @ts-expect-error: ponyfill doesn't have the full typings 33 + id: 'repo-export', 34 + startIn: 'downloads', 35 + types: [ 36 + { 37 + description: 'CAR archive file', 38 + accept: { 'application/vnd.ipld.car': ['.car'] }, 39 + }, 40 + ], 41 + }).catch((err) => { 42 + if (err instanceof DOMException && err.name === 'AbortError') { 43 + return undefined; 44 + } 45 + throw err; 46 + }); 47 + 48 + if (!fd) { 49 + setExportStatus(); 50 + return null; 51 + } 52 + 53 + const writable = await fd.createWritable(); 54 + 55 + setExportStatus('Downloading repository...'); 56 + 57 + const sourceClient = new Client({ handler: simpleFetchHandler({ service: pdsUrl }) }); 58 + const response = await sourceClient.get('com.atproto.sync.getRepo', { 59 + as: 'stream', 60 + params: { did }, 61 + }); 62 + 63 + if (!response.ok) { 64 + throw new Error(`Failed to download repository: ${response.status}`); 65 + } 66 + 67 + let size = 0; 68 + for await (const chunk of iterateStream(response.data)) { 69 + size += chunk.length; 70 + await writable.write(chunk); 71 + setExportStatus(`Downloading repository... (${formatBytes(size)})`); 72 + } 73 + 74 + await writable.close(); 75 + setExportStatus(`Exported ${formatBytes(size)}`); 76 + return size; 77 + }, 78 + onMutate() { 79 + setExportStatus(); 80 + }, 81 + onError(err) { 82 + console.error(err); 83 + setExportStatus(); 84 + }, 85 + }); 86 + 87 + const importFromFileMutation = createMutation({ 88 + async mutationFn({ manager }: { manager: CredentialManager }) { 89 + setImportStatus('Waiting for file picker...'); 90 + 91 + const [fd] = await showOpenFilePicker({ 92 + // @ts-expect-error: ponyfill doesn't have the full typings 93 + id: 'repo-import', 94 + types: [ 95 + { 96 + description: 'CAR archive file', 97 + accept: { 'application/vnd.ipld.car': ['.car'] }, 98 + }, 99 + ], 100 + }).catch((err) => { 101 + if (err instanceof DOMException && err.name === 'AbortError') { 102 + return [undefined]; 103 + } 104 + throw err; 105 + }); 106 + 107 + if (!fd) { 108 + setImportStatus(); 109 + return null; 110 + } 111 + 112 + const file = await fd.getFile(); 113 + 114 + setImportStatus(`Uploading repository (${formatBytes(file.size)})...`); 115 + 116 + const destClient = new Client({ handler: manager }); 117 + const importResp = await destClient.post('com.atproto.repo.importRepo', { 118 + as: null, 119 + input: file, 120 + headers: { 121 + 'content-type': 'application/vnd.ipld.car', 122 + }, 123 + }); 124 + 125 + if (!importResp.ok) { 126 + throw new Error(`Failed to import repository: ${importResp.status}`); 127 + } 128 + 129 + // Check account status to get record count 130 + const status = await ok(destClient.get('com.atproto.server.checkAccountStatus', {})); 131 + setImportedRecords(status.indexedRecords); 132 + 133 + setImportStatus(`Imported successfully`); 134 + return status.indexedRecords; 135 + }, 136 + onMutate() { 137 + setImportStatus(); 138 + setImportedRecords(); 139 + }, 140 + onError(err) { 141 + console.error(err); 142 + setImportStatus(); 143 + }, 144 + }); 145 + 146 + const importFromSourceMutation = createMutation({ 147 + async mutationFn({ 148 + sourcePdsUrl, 149 + sourceDid, 150 + destManager, 151 + }: { 152 + sourcePdsUrl: string; 153 + sourceDid: Did; 154 + destManager: CredentialManager; 155 + }) { 156 + setImportStatus('Downloading from source PDS...'); 157 + 158 + const sourceClient = new Client({ handler: simpleFetchHandler({ service: sourcePdsUrl }) }); 159 + const response = await sourceClient.get('com.atproto.sync.getRepo', { 160 + as: 'bytes', 161 + params: { did: sourceDid }, 162 + }); 163 + 164 + if (!response.ok) { 165 + throw new Error(`Failed to download repository: ${response.status}`); 166 + } 167 + 168 + setImportStatus(`Uploading to destination (${formatBytes(response.data.length)})...`); 169 + 170 + const destClient = new Client({ handler: destManager }); 171 + const importResp = await destClient.post('com.atproto.repo.importRepo', { 172 + as: null, 173 + input: response.data, 174 + headers: { 175 + 'content-type': 'application/vnd.ipld.car', 176 + }, 177 + }); 178 + 179 + if (!importResp.ok) { 180 + throw new Error(`Failed to import repository: ${importResp.status}`); 181 + } 182 + 183 + // Check account status to get record count 184 + const status = await ok(destClient.get('com.atproto.server.checkAccountStatus', {})); 185 + setImportedRecords(status.indexedRecords); 186 + 187 + setImportStatus(`Imported successfully`); 188 + return status.indexedRecords; 189 + }, 190 + onMutate() { 191 + setImportStatus(); 192 + setImportedRecords(); 193 + }, 194 + onError(err) { 195 + console.error(err); 196 + setImportStatus(); 197 + }, 198 + }); 199 + 200 + const isExporting = () => exportMutation.isPending; 201 + const isImporting = () => importFromFileMutation.isPending || importFromSourceMutation.isPending; 202 + 203 + return ( 204 + <Accordion title="Repository"> 205 + <Subsection title="Export from source"> 206 + <p class="text-sm text-gray-600"> 207 + Download the repository as a CAR file for backup or manual import. 208 + </p> 209 + 210 + <Show when={source()} fallback={<p class="text-sm text-gray-500">Resolve source account first.</p>}> 211 + {(src) => ( 212 + <> 213 + <div class="flex items-center gap-3"> 214 + <Button 215 + onClick={() => exportMutation.mutate({ pdsUrl: src().pdsUrl, did: src().did })} 216 + disabled={isExporting()} 217 + > 218 + {isExporting() ? 'Exporting...' : 'Export to file'} 219 + </Button> 220 + <Show when={exportStatus()}> 221 + <span class="text-sm text-gray-600">{exportStatus()}</span> 222 + </Show> 223 + </div> 224 + 225 + <Show when={exportMutation.isError}> 226 + <p class="text-sm text-red-600">{`${exportMutation.error}`}</p> 227 + </Show> 228 + </> 229 + )} 230 + </Show> 231 + </Subsection> 232 + 233 + <Subsection title="Import to destination"> 234 + <p class="text-sm text-gray-600">Upload a repository CAR file or transfer directly from source.</p> 235 + 236 + <Show 237 + when={destination()?.manager} 238 + fallback={<p class="text-sm text-gray-500">Sign in to destination account first.</p>} 239 + > 240 + {(manager) => ( 241 + <> 242 + <div class="flex flex-wrap items-center gap-3"> 243 + <Button 244 + onClick={() => importFromFileMutation.mutate({ manager: manager() })} 245 + disabled={isImporting()} 246 + > 247 + {isImporting() ? 'Importing...' : 'Import from file'} 248 + </Button> 249 + 250 + <Show when={source()}> 251 + {(src) => ( 252 + <Button 253 + variant="secondary" 254 + onClick={() => 255 + importFromSourceMutation.mutate({ 256 + sourcePdsUrl: src().pdsUrl, 257 + sourceDid: src().did, 258 + destManager: manager(), 259 + }) 260 + } 261 + disabled={isImporting()} 262 + > 263 + Transfer from source 264 + </Button> 265 + )} 266 + </Show> 267 + </div> 268 + 269 + <Show when={importStatus()}> 270 + <div class="flex items-center gap-2"> 271 + <span class="text-sm text-gray-600">{importStatus()}</span> 272 + <Show when={importedRecords() !== undefined}> 273 + <StatusBadge variant="success">{importedRecords()} records</StatusBadge> 274 + </Show> 275 + </div> 276 + </Show> 277 + 278 + <Show when={importFromFileMutation.isError || importFromSourceMutation.isError}> 279 + <p class="text-sm text-red-600"> 280 + {`${importFromFileMutation.error || importFromSourceMutation.error}`} 281 + </p> 282 + </Show> 283 + </> 284 + )} 285 + </Show> 286 + </Subsection> 287 + </Accordion> 288 + ); 289 + }; 290 + 291 + export default RepositorySection;
+265
src/views/account/account-migrate/sections/source-account.tsx
··· 1 + import { createSignal, Show } from 'solid-js'; 2 + 3 + import { type AtpAccessJwt, ClientResponseError, CredentialManager } from '@atcute/client'; 4 + import { getPdsEndpoint, isAtprotoDid } from '@atcute/identity'; 5 + import { isHandle, type AtprotoDid } from '@atcute/lexicons/syntax'; 6 + 7 + import { getDidDocument } from '~/api/queries/did-doc'; 8 + import { resolveHandleViaAppView } from '~/api/queries/handle'; 9 + import { formatTotpCode, TOTP_RE } from '~/api/utils/auth'; 10 + import { decodeJwt } from '~/api/utils/jwt'; 11 + 12 + import { createMutation } from '~/lib/utils/mutation'; 13 + 14 + import { Accordion, StatusBadge, Subsection } from '~/components/accordion'; 15 + import Button from '~/components/inputs/button'; 16 + import TextInput from '~/components/inputs/text-input'; 17 + 18 + import { useMigration } from '../context'; 19 + 20 + interface SourceAccountSectionProps { 21 + onStarted?: () => void; 22 + } 23 + 24 + class InsufficientLoginError extends Error {} 25 + 26 + const SourceAccountSection = (props: SourceAccountSectionProps) => { 27 + const { source, setSource } = useMigration(); 28 + 29 + // Resolve state 30 + const [identifier, setIdentifier] = createSignal(''); 31 + const [resolveError, setResolveError] = createSignal<string>(); 32 + 33 + // Auth state 34 + const [password, setPassword] = createSignal(''); 35 + const [otp, setOtp] = createSignal(''); 36 + const [isTotpRequired, setIsTotpRequired] = createSignal(false); 37 + const [authError, setAuthError] = createSignal<string>(); 38 + 39 + const resolveMutation = createMutation({ 40 + async mutationFn({ identifier }: { identifier: string }) { 41 + let did: AtprotoDid; 42 + if (isAtprotoDid(identifier)) { 43 + did = identifier; 44 + } else if (isHandle(identifier)) { 45 + did = await resolveHandleViaAppView({ handle: identifier }); 46 + } else { 47 + throw new Error(`${identifier} is not a valid DID or handle`); 48 + } 49 + 50 + const didDoc = await getDidDocument({ did }); 51 + const pdsUrl = getPdsEndpoint(didDoc); 52 + 53 + if (!pdsUrl) { 54 + throw new Error(`No PDS endpoint found in DID document`); 55 + } 56 + 57 + return { did, didDoc, pdsUrl }; 58 + }, 59 + onMutate() { 60 + setResolveError(); 61 + }, 62 + onSuccess({ did, didDoc, pdsUrl }) { 63 + setSource({ did, didDoc, pdsUrl, manager: null }); 64 + props.onStarted?.(); 65 + }, 66 + onError(err) { 67 + if (err instanceof ClientResponseError) { 68 + if (err.error === 'InvalidRequest' && err.description?.includes('resolve handle')) { 69 + setResolveError(`Can't resolve handle, is it typed correctly?`); 70 + return; 71 + } 72 + } 73 + console.error(err); 74 + setResolveError(`${err}`); 75 + }, 76 + }); 77 + 78 + const authMutation = createMutation({ 79 + async mutationFn({ pdsUrl, did, password, otp }: { pdsUrl: string; did: string; password: string; otp: string }) { 80 + const manager = new CredentialManager({ service: pdsUrl }); 81 + const session = await manager.login({ 82 + identifier: did, 83 + password: password, 84 + code: formatTotpCode(otp), 85 + }); 86 + 87 + const decoded = decodeJwt(session.accessJwt) as AtpAccessJwt; 88 + if (decoded.scope !== 'com.atproto.access') { 89 + throw new InsufficientLoginError(`You need to sign in with a main password, not an app password`); 90 + } 91 + 92 + return manager; 93 + }, 94 + onMutate() { 95 + setAuthError(); 96 + }, 97 + onSuccess(manager) { 98 + setSource({ ...source()!, manager }); 99 + setPassword(''); 100 + setOtp(''); 101 + setIsTotpRequired(false); 102 + }, 103 + onError(err) { 104 + if (err instanceof ClientResponseError) { 105 + if (err.error === 'AuthFactorTokenRequired') { 106 + setOtp(''); 107 + setIsTotpRequired(true); 108 + return; 109 + } 110 + if (err.error === 'AuthenticationRequired') { 111 + setAuthError(`Invalid identifier or password`); 112 + return; 113 + } 114 + if (err.error === 'AccountTakedown') { 115 + setAuthError(`Account has been taken down`); 116 + return; 117 + } 118 + if (err.description?.includes('Token is invalid')) { 119 + setAuthError(`Invalid one-time confirmation code`); 120 + setIsTotpRequired(true); 121 + return; 122 + } 123 + } 124 + if (err instanceof InsufficientLoginError) { 125 + setAuthError(err.message); 126 + return; 127 + } 128 + console.error(err); 129 + setAuthError(`${err}`); 130 + }, 131 + }); 132 + 133 + const isResolved = () => source() !== null; 134 + const isAuthenticated = () => source()?.manager != null; 135 + 136 + return ( 137 + <Accordion title="Source Account" defaultOpen> 138 + <Subsection title="Resolve identity"> 139 + <Show when={!isResolved()}> 140 + <form 141 + onSubmit={(ev) => { 142 + ev.preventDefault(); 143 + resolveMutation.mutate({ identifier: identifier() }); 144 + }} 145 + class="flex flex-col gap-3" 146 + > 147 + <TextInput 148 + label="Handle or DID" 149 + placeholder="alice.bsky.social" 150 + value={identifier()} 151 + required 152 + autofocus 153 + onChange={setIdentifier} 154 + /> 155 + 156 + <Show when={resolveError()}> 157 + <p class="text-sm text-red-600">{resolveError()}</p> 158 + </Show> 159 + 160 + <div> 161 + <Button type="submit" disabled={resolveMutation.isPending}> 162 + {resolveMutation.isPending ? 'Resolving...' : 'Resolve'} 163 + </Button> 164 + </div> 165 + </form> 166 + </Show> 167 + 168 + <Show when={isResolved()}> 169 + <div class="flex flex-col gap-2 text-sm"> 170 + <p> 171 + <span class="text-gray-500">DID:</span>{' '} 172 + <span class="font-mono">{source()!.did}</span> 173 + </p> 174 + <p> 175 + <span class="text-gray-500">PDS:</span>{' '} 176 + <span class="font-mono">{source()!.pdsUrl}</span> 177 + </p> 178 + <div class="mt-1"> 179 + <button 180 + type="button" 181 + onClick={() => setSource(null)} 182 + class="text-sm text-purple-800 hover:underline" 183 + > 184 + Change account 185 + </button> 186 + </div> 187 + </div> 188 + </Show> 189 + </Subsection> 190 + 191 + <Show when={isResolved()}> 192 + <Subsection title="Authenticate"> 193 + <p class="text-sm text-gray-600"> 194 + Authentication is required for some operations like exporting preferences or signing PLC operations. 195 + </p> 196 + 197 + <Show when={!isAuthenticated()}> 198 + <form 199 + onSubmit={(ev) => { 200 + ev.preventDefault(); 201 + const src = source()!; 202 + authMutation.mutate({ 203 + pdsUrl: src.pdsUrl, 204 + did: src.did, 205 + password: password(), 206 + otp: otp(), 207 + }); 208 + }} 209 + class="flex flex-col gap-3" 210 + > 211 + <TextInput 212 + label="Main password" 213 + blurb="Your credentials stay entirely within your browser." 214 + type="password" 215 + value={password()} 216 + required 217 + onChange={setPassword} 218 + /> 219 + 220 + <Show when={isTotpRequired()}> 221 + <TextInput 222 + label="One-time confirmation code" 223 + blurb="A code has been sent to your email address." 224 + type="text" 225 + autocomplete="one-time-code" 226 + pattern={TOTP_RE.source} 227 + placeholder="AAAAA-BBBBB" 228 + value={otp()} 229 + required 230 + onChange={setOtp} 231 + monospace 232 + /> 233 + </Show> 234 + 235 + <Show when={authError()}> 236 + <p class="text-sm text-red-600">{authError()}</p> 237 + </Show> 238 + 239 + <div> 240 + <Button type="submit" disabled={authMutation.isPending}> 241 + {authMutation.isPending ? 'Signing in...' : 'Sign in'} 242 + </Button> 243 + </div> 244 + </form> 245 + </Show> 246 + 247 + <Show when={isAuthenticated()}> 248 + <div class="flex items-center gap-2"> 249 + <StatusBadge variant="success">Signed in</StatusBadge> 250 + <button 251 + type="button" 252 + onClick={() => setSource({ ...source()!, manager: null })} 253 + class="text-sm text-purple-800 hover:underline" 254 + > 255 + Sign out 256 + </button> 257 + </div> 258 + </Show> 259 + </Subsection> 260 + </Show> 261 + </Accordion> 262 + ); 263 + }; 264 + 265 + export default SourceAccountSection;
+46 -33
src/views/blob/blob-export.tsx
··· 1 1 import { FileSystemWritableFileStream, showSaveFilePicker } from 'native-file-system-adapter'; 2 2 import { createSignal } from 'solid-js'; 3 3 4 - import { simpleFetchHandler, XRPC, XRPCError } from '@atcute/client'; 5 - import { type AtprotoDid, getPdsEndpoint, isAtprotoDid, isHandle } from '@atcute/identity'; 4 + import { Client, ClientResponseError, ok, simpleFetchHandler } from '@atcute/client'; 5 + import { getPdsEndpoint, isAtprotoDid } from '@atcute/identity'; 6 + import { isHandle, type AtprotoDid } from '@atcute/lexicons/syntax'; 6 7 import { writeTarEntry } from '@mary/tar'; 7 8 8 9 import { getDidDocument } from '~/api/queries/did-doc'; 9 10 import { resolveHandleViaAppView, resolveHandleViaPds } from '~/api/queries/handle'; 10 11 import { isServiceUrlString } from '~/api/types/strings'; 11 - import { DID_OR_HANDLE_RE } from '~/api/utils/strings'; 12 12 13 13 import { useTitle } from '~/lib/navigation/router'; 14 14 import { makeAbortable } from '~/lib/utils/abortable'; ··· 17 17 import Button from '~/components/inputs/button'; 18 18 import TextInput from '~/components/inputs/text-input'; 19 19 import Logger, { createLogger } from '~/components/logger'; 20 + import PageHeader from '~/components/page-header'; 20 21 21 22 const BlobExportPage = () => { 22 23 const logger = createLogger(); ··· 65 66 service = endpoint; 66 67 } 67 68 68 - const rpc = new XRPC({ handler: simpleFetchHandler({ service }) }); 69 + // const rpc = new XRPC({ handler: simpleFetchHandler({ service }) }); 70 + const client = new Client({ handler: simpleFetchHandler({ service }) }); 69 71 70 72 // Grab a list of blobs 71 73 let blobs: string[] = []; ··· 74 76 75 77 let cursor: string | undefined; 76 78 do { 77 - const { data } = await rpc.get('com.atproto.sync.listBlobs', { 78 - signal, 79 - params: { did, cursor, limit: 1_000 }, 80 - }); 79 + const data = await ok( 80 + client.get('com.atproto.sync.listBlobs', { 81 + signal, 82 + params: { did, cursor, limit: 1_000 }, 83 + }), 84 + ); 81 85 82 86 cursor = data.cursor; 83 87 blobs = blobs.concat(data.cids); ··· 151 155 attempts++; 152 156 153 157 try { 154 - const { data } = await rpc.get('com.atproto.sync.getBlob', { 158 + const response = await client.get('com.atproto.sync.getBlob', { 155 159 signal, 160 + as: 'bytes', 156 161 params: { did, cid }, 157 162 }); 158 163 159 - return data; 160 - } catch (err) { 161 - if (attempts > 3) { 162 - throw err; 164 + if (response.ok) { 165 + return response.data; 163 166 } 164 167 165 - if (err instanceof XRPCError) { 166 - if (err.status === 400) { 167 - if (err.message === 'Blob not found') { 168 - console.warn(`Blob ${cid} not found`); 169 - return; 170 - } 171 - } else if (err.status === 429) { 172 - const reset = err.headers?.['ratelimit-reset']; 168 + if (response.status === 400) { 169 + // If the PDS says it can't find the blob, stop right here. 170 + if (response.data.message === 'Blob not found') { 171 + logger.warn(`Blob ${cid} not found`); 172 + return undefined; 173 + } 174 + } else if (response.status === 429) { 175 + // Not exposed by CORS, hoping that someday it will 176 + const reset = response.headers.get('ratelimit-reset'); 173 177 174 - if (reset !== undefined) { 175 - logger.warn(`Ratelimit exceeded when downloading ${cid}, waiting`); 178 + logger.warn(`Ratelimit exceeded when downloading ${cid}, waiting`); 176 179 177 - const refreshAt = +reset * 1_000; 178 - const delta = refreshAt - Date.now(); 180 + if (reset !== null) { 181 + const refreshAt = +reset * 1_000; 182 + const delta = refreshAt - Date.now(); 179 183 180 - await sleep(delta); 181 - } 184 + await sleep(delta); 185 + } else { 186 + await sleep(10_000); 182 187 } 183 188 } 189 + 190 + if (attempts < 3) { 191 + continue; 192 + } 193 + 194 + throw new ClientResponseError(response); 195 + } catch (err) { 196 + // Network errors, etc 197 + if (attempts < 3) { 198 + continue; 199 + } 200 + 201 + throw err; 184 202 } 185 203 } 186 204 }; ··· 212 230 213 231 return ( 214 232 <> 215 - <div class="p-4"> 216 - <h1 class="text-lg font-bold text-purple-800">Export blobs</h1> 217 - <p class="text-gray-600">Download all blobs from an account into a tarball</p> 218 - </div> 219 - <hr class="mx-4 border-gray-300" /> 233 + <PageHeader title="Export blobs" subtitle="Download all blobs from an account into a tarball" /> 220 234 221 235 <form 222 236 onSubmit={(ev) => { ··· 267 281 type="text" 268 282 name="ident" 269 283 autocomplete="username" 270 - pattern={/* @once */ DID_OR_HANDLE_RE.source} 271 284 placeholder="paul.bsky.social" 272 285 autofocus 273 286 />
+10 -11
src/views/bluesky/threadgate-applicator/page.tsx
··· 1 1 import { createEffect, createSignal, onCleanup } from 'solid-js'; 2 2 3 + import { AppBskyFeedDefs, AppBskyFeedThreadgate } from '@atcute/bluesky'; 3 4 import { CredentialManager } from '@atcute/client'; 4 - import { AppBskyFeedDefs, AppBskyFeedThreadgate } from '@atcute/client/lexicons'; 5 - import { DidDocument } from '@atcute/identity'; 5 + import type { DidDocument } from '@atcute/identity'; 6 6 7 - import { UnwrapArray } from '~/api/utils/types'; 7 + import type { UnwrapArray } from '~/api/utils/types'; 8 8 9 9 import { history } from '~/globals/navigation'; 10 10 11 11 import { useTitle } from '~/lib/navigation/router'; 12 12 13 + import PageHeader from '~/components/page-header'; 13 14 import { Wizard } from '~/components/wizard'; 14 15 15 16 import Step1_HandleInput from './steps/step1_handle-input'; ··· 18 19 import Step4_Confirmation from './steps/step4_confirmation'; 19 20 import Step5_Finished from './steps/step5_finished'; 20 21 21 - export interface ThreadgateState 22 - extends Pick<AppBskyFeedThreadgate.Record, 'allow' | 'hiddenReplies' | 'createdAt'> { 22 + export interface ThreadgateState extends Pick< 23 + AppBskyFeedThreadgate.Main, 24 + 'allow' | 'hiddenReplies' | 'createdAt' 25 + > { 23 26 uri: string; 24 27 } 25 28 26 - export type ThreadgateRule = UnwrapArray<AppBskyFeedThreadgate.Record['allow']>; 29 + export type ThreadgateRule = UnwrapArray<AppBskyFeedThreadgate.Main['allow']>; 27 30 28 31 export interface ThreadItem { 29 32 post: AppBskyFeedDefs.PostView; ··· 78 81 79 82 return ( 80 83 <> 81 - <div class="p-4"> 82 - <h1 class="text-lg font-bold text-purple-800">Retroactive thread gating</h1> 83 - <p class="text-gray-600">Set reply permissions on all of your past Bluesky posts</p> 84 - </div> 85 - <hr class="mx-4 border-gray-300" /> 84 + <PageHeader title="Retroactive thread gating" subtitle="Set reply permissions on all of your past Bluesky posts" /> 86 85 87 86 <Wizard<ThreadgateApplicatorConstraints> 88 87 initialStep="Step1_HandleInput"
+18 -16
src/views/bluesky/threadgate-applicator/steps/step1_handle-input.tsx
··· 1 1 import { createSignal } from 'solid-js'; 2 2 3 - import type { AppBskyFeedThreadgate } from '@atcute/client/lexicons'; 4 - import { type AtprotoDid, isAtprotoDid, isHandle } from '@atcute/identity'; 3 + import type { AppBskyFeedThreadgate } from '@atcute/bluesky'; 4 + import { ok } from '@atcute/client'; 5 + import { isAtprotoDid } from '@atcute/identity'; 6 + import { isHandle, type AtprotoDid } from '@atcute/lexicons/syntax'; 5 7 6 8 import { getDidDocument } from '~/api/queries/did-doc'; 7 9 import { resolveHandleViaAppView } from '~/api/queries/handle'; 8 - import { DID_OR_HANDLE_RE } from '~/api/utils/strings'; 9 10 10 11 import { appViewRpc } from '~/globals/rpc'; 11 12 ··· 13 14 14 15 import Button from '~/components/inputs/button'; 15 16 import TextInput from '~/components/inputs/text-input'; 16 - import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard'; 17 + import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard'; 17 18 18 - import { ThreadgateApplicatorConstraints, ThreadgateState, ThreadItem } from '../page'; 19 + import type { ThreadgateApplicatorConstraints, ThreadgateState, ThreadItem } from '../page'; 19 20 import { sortThreadgateState } from '../utils'; 20 21 21 22 class NoThreadsError extends Error {} ··· 50 51 51 52 let cursor: string | undefined; 52 53 do { 53 - const { data } = await appViewRpc.get('app.bsky.feed.getAuthorFeed', { 54 - signal, 55 - params: { 56 - actor: did, 57 - filter: 'posts_no_replies', 58 - limit: 100, 59 - cursor, 60 - }, 61 - }); 54 + const data = await ok( 55 + appViewRpc.get('app.bsky.feed.getAuthorFeed', { 56 + signal, 57 + params: { 58 + actor: did, 59 + filter: 'posts_no_replies', 60 + limit: 100, 61 + cursor, 62 + }, 63 + }), 64 + ); 62 65 63 66 cursor = data.cursor; 64 67 ··· 83 86 let threadgate: ThreadgateState | null = null; 84 87 85 88 if (tg?.record) { 86 - const record = tg.record as AppBskyFeedThreadgate.Record; 89 + const record = tg.record as AppBskyFeedThreadgate.Main; 87 90 88 91 const allow = record?.allow; 89 92 const hiddenReplies = record?.hiddenReplies; ··· 153 156 placeholder="paul.bsky.social" 154 157 value={identifier()} 155 158 required 156 - pattern={/* @once */ DID_OR_HANDLE_RE.source} 157 159 autofocus={isActive()} 158 160 onChange={setIdentifier} 159 161 />
+19 -26
src/views/bluesky/threadgate-applicator/steps/step2_rules-input.tsx
··· 1 1 import { batch, createMemo, createSignal, For, Show } from 'solid-js'; 2 2 3 - import { AppBskyFeedThreadgate, Brand } from '@atcute/client/lexicons'; 4 - 5 - import { UnwrapArray } from '~/api/utils/types'; 3 + import { AppBskyFeedThreadgate } from '@atcute/bluesky'; 4 + import { ok } from '@atcute/client'; 5 + import type { $type } from '@atcute/lexicons'; 6 6 7 7 import { appViewRpc } from '~/globals/rpc'; 8 8 ··· 11 11 import { createQuery } from '~/lib/utils/query'; 12 12 13 13 import RadioInput from '~/components/inputs/radio-input'; 14 - import { Stage, StageActions, WizardStepProps } from '~/components/wizard'; 14 + import { Stage, StageActions, type WizardStepProps } from '~/components/wizard'; 15 15 16 16 import CircularProgressView from '~/components/circular-progress-view'; 17 17 import Button from '~/components/inputs/button'; 18 18 import ToggleInput from '~/components/inputs/toggle-input'; 19 19 20 - import { ThreadgateApplicatorConstraints } from '../page'; 20 + import type { ThreadgateApplicatorConstraints, ThreadgateRule } from '../page'; 21 21 import { sortThreadgateAllow } from '../utils'; 22 22 23 23 const enum FilterType { ··· 30 30 NO_ONE = 'no_one', 31 31 CUSTOM = 'custom', 32 32 } 33 - 34 - type ThreadRule = UnwrapArray<AppBskyFeedThreadgate.Record['allow']>; 35 33 36 34 const Step2_RulesInput = ({ 37 35 data, ··· 41 39 }: WizardStepProps<ThreadgateApplicatorConstraints, 'Step2_RulesInput'>) => { 42 40 const [filter, setFilter] = createSignal(FilterType.MISSING_ONLY); 43 41 44 - const [threadRules, _setThreadRules] = createSignal<ThreadRule[] | undefined>([ 42 + const [threadRules, _setThreadRules] = createSignal<ThreadgateRule[] | undefined>([ 45 43 { $type: 'app.bsky.feed.threadgate#followingRule' }, 46 44 { $type: 'app.bsky.feed.threadgate#mentionRule' }, 47 45 ]); ··· 64 62 () => data.profile.didDoc.id, 65 63 async (did, signal) => { 66 64 const lists = await accumulate(async (cursor) => { 67 - const { data } = await appViewRpc.get('app.bsky.graph.getLists', { 68 - signal, 69 - params: { 70 - actor: did, 71 - cursor, 72 - limit: 100, 73 - }, 74 - }); 65 + const data = await ok( 66 + appViewRpc.get('app.bsky.graph.getLists', { 67 + signal, 68 + params: { 69 + actor: did, 70 + cursor, 71 + limit: 100, 72 + }, 73 + }), 74 + ); 75 75 76 76 return { 77 77 cursor: data.cursor, ··· 121 121 ); 122 122 }); 123 123 124 - const hasThreadRule = (predicate: ThreadRule): boolean => { 124 + const hasThreadRule = (predicate: ThreadgateRule): boolean => { 125 125 return !!threadRules()?.find((rule) => dequal(rule, predicate)); 126 126 }; 127 127 128 - const setCustomThreadRules = (next: ThreadRule[] | undefined) => { 128 + const setCustomThreadRules = (next: ThreadgateRule[] | undefined) => { 129 129 batch(() => { 130 130 _setThreadRules(next); 131 131 setThreadRulesPreset(ThreadRulePreset.CUSTOM); ··· 226 226 } 227 227 > 228 228 {(list) => { 229 - const rule: Brand.Union<AppBskyFeedThreadgate.ListRule> = { 229 + const rule: $type.enforce<AppBskyFeedThreadgate.ListRule> = { 230 230 $type: 'app.bsky.feed.threadgate#listRule', 231 231 list: list.uri, 232 232 }; ··· 255 255 blurb={ 256 256 <> 257 257 <span>This will apply to {filteredThreads().length} threads. </span> 258 - {/* <button 259 - type="button" 260 - hidden={filteredThreads().length < 1} 261 - class="font-medium text-purple-800 hover:underline" 262 - > 263 - View 264 - </button> */} 265 258 </> 266 259 } 267 260 value={filter()}
+2 -2
src/views/bluesky/threadgate-applicator/steps/step3_authentication.tsx
··· 2 2 3 3 import { CredentialManager } from '@atcute/client'; 4 4 5 - import { WizardStepProps } from '~/components/wizard'; 5 + import type { WizardStepProps } from '~/components/wizard'; 6 6 import BlueskyLoginStep from '~/components/wizards/bluesky-login-step'; 7 7 8 - import { ThreadgateApplicatorConstraints } from '../page'; 8 + import type { ThreadgateApplicatorConstraints } from '../page'; 9 9 10 10 const Step3_Authentication = ({ 11 11 data,
+70 -52
src/views/bluesky/threadgate-applicator/steps/step4_confirmation.tsx
··· 1 1 import { createSignal, Show } from 'solid-js'; 2 2 3 - import { XRPC, XRPCError } from '@atcute/client'; 4 - import type { AppBskyFeedThreadgate, ComAtprotoRepoApplyWrites } from '@atcute/client/lexicons'; 3 + import type { ComAtprotoRepoApplyWrites } from '@atcute/atproto'; 4 + import type { AppBskyFeedThreadgate } from '@atcute/bluesky'; 5 + import { Client, ClientResponseError } from '@atcute/client'; 6 + import { parseCanonicalResourceUri } from '@atcute/lexicons'; 5 7 import { chunked } from '@mary/array-fns'; 6 - 7 - import { parseAddressedAtUri } from '~/api/utils/at-uri'; 8 8 9 9 import { dequal } from '~/lib/utils/dequal'; 10 10 import { createMutation } from '~/lib/utils/mutation'; ··· 12 12 import Button from '~/components/inputs/button'; 13 13 import ToggleInput from '~/components/inputs/toggle-input'; 14 14 import Logger, { createLogger } from '~/components/logger'; 15 - import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard'; 15 + import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard'; 16 16 17 - import { ThreadgateApplicatorConstraints } from '../page'; 17 + import type { ThreadgateApplicatorConstraints } from '../page'; 18 18 19 19 const Step4_Confirmation = ({ 20 20 data, ··· 34 34 logger.log(`Preparing writes`); 35 35 36 36 const rules = data.rules; 37 - const writes: ComAtprotoRepoApplyWrites.Input['writes'] = []; 37 + const writes: ComAtprotoRepoApplyWrites.$input['writes'] = []; 38 38 39 39 const now = new Date().toISOString(); 40 40 for (const { post, threadgate } of data.threads) { 41 41 if (threadgate === null) { 42 42 if (rules !== undefined) { 43 - const { rkey } = parseAddressedAtUri(post.uri); 43 + const postUri = parseCanonicalResourceUri(post.uri); 44 + if (!postUri.ok) { 45 + throw new Error(`failed to parse ${post.uri}`); 46 + } 44 47 45 - const record: AppBskyFeedThreadgate.Record = { 48 + const record: AppBskyFeedThreadgate.Main = { 46 49 $type: 'app.bsky.feed.threadgate', 47 50 createdAt: now, 48 51 post: post.uri, ··· 53 56 writes.push({ 54 57 $type: 'com.atproto.repo.applyWrites#create', 55 58 collection: 'app.bsky.feed.threadgate', 56 - rkey: rkey, 59 + rkey: postUri.value.rkey, 57 60 value: record, 58 61 }); 59 62 } 60 63 } else { 61 64 if (rules === undefined && !threadgate.hiddenReplies?.length) { 62 - const { rkey } = parseAddressedAtUri(threadgate.uri); 65 + const threadgateUri = parseCanonicalResourceUri(threadgate.uri); 66 + if (!threadgateUri.ok) { 67 + throw new Error(`failed to parse ${threadgate.uri}`); 68 + } 63 69 64 70 writes.push({ 65 71 $type: 'com.atproto.repo.applyWrites#delete', 66 72 collection: 'app.bsky.feed.threadgate', 67 - rkey: rkey, 73 + rkey: threadgateUri.value.rkey, 68 74 }); 69 75 } else if (!dequal(threadgate.allow, rules)) { 70 - const { rkey } = parseAddressedAtUri(threadgate.uri); 76 + const threadgateUri = parseCanonicalResourceUri(threadgate.uri); 77 + if (!threadgateUri.ok) { 78 + throw new Error(`failed to parse ${threadgate.uri}`); 79 + } 71 80 72 - const record: AppBskyFeedThreadgate.Record = { 81 + const record: AppBskyFeedThreadgate.Main = { 73 82 $type: 'app.bsky.feed.threadgate', 74 83 createdAt: threadgate.createdAt, 75 84 post: post.uri, ··· 80 89 writes.push({ 81 90 $type: 'com.atproto.repo.applyWrites#update', 82 91 collection: 'app.bsky.feed.threadgate', 83 - rkey: rkey, 92 + rkey: threadgateUri.value.rkey, 84 93 value: record, 85 94 }); 86 95 } ··· 90 99 logger.log(`${writes.length} write operations to apply`); 91 100 92 101 const did = data.profile.didDoc.id; 93 - const rpc = new XRPC({ handler: data.manager }); 94 - 95 - const RATELIMIT_POINT_LIMIT = 150 * 3; 102 + const client = new Client({ handler: data.manager }); 96 103 97 104 { 98 105 using progress = logger.progress(`Applying writes`); 99 106 100 107 let written = 0; 101 108 for (const chunk of chunked(writes, 200)) { 102 - try { 103 - const { headers } = await rpc.call('com.atproto.repo.applyWrites', { 104 - data: { 105 - repo: did, 106 - writes: chunk, 107 - }, 108 - }); 109 + let attempts = 0; 109 110 110 - written += chunk.length; 111 - progress.update(`Applying writes (${written} applied)`); 111 + while (true) { 112 + if (attempts > 0) { 113 + await sleep(2_000); 114 + } 112 115 113 - if ('ratelimit-remaining' in headers) { 114 - const remaining = +headers['ratelimit-remaining']; 115 - const reset = +headers['ratelimit-reset'] * 1_000; 116 + attempts++; 116 117 117 - if (remaining < RATELIMIT_POINT_LIMIT) { 118 - // add some delay to be sure 119 - const delta = reset - Date.now() + 5_000; 120 - using _progress = logger.progress(`Reached ratelimit, waiting ${delta}ms`); 118 + try { 119 + const response = await client.post('com.atproto.repo.applyWrites', { 120 + input: { 121 + repo: did, 122 + writes: chunk, 123 + }, 124 + }); 121 125 122 - await new Promise((resolve) => setTimeout(resolve, delta)); 126 + if (response.ok) { 127 + written += chunk.length; 128 + progress.update(`Applying writes (${written} applied)`); 129 + break; 123 130 } 124 - } 125 - } catch (err) { 126 - if (!(err instanceof XRPCError) || err.kind !== 'RateLimitExceeded') { 127 - throw err; 128 - } 131 + 132 + if (response.status === 429) { 133 + // not exposed by CORS, hoping that someday it will 134 + const reset = response.headers.get('ratelimit-reset'); 135 + 136 + using _progress = logger.progress(`Ratelimited, waiting`); 137 + 138 + if (reset !== null) { 139 + const refreshAt = +reset * 1_000; 140 + const delta = refreshAt - Date.now(); 129 141 130 - const headers = err.headers; 131 - if ('ratelimit-remaining' in headers) { 132 - const remaining = +headers['ratelimit-remaining']; 133 - const reset = +headers['ratelimit-reset'] * 1_000; 142 + await sleep(delta); 143 + } else { 144 + await sleep(10_000); 145 + } 146 + } 134 147 135 - if (remaining < RATELIMIT_POINT_LIMIT) { 136 - // add some delay to be sure 137 - const delta = reset - Date.now() + 5_000; 138 - using _progress = logger.progress(`Ratelimited, waiting ${delta}ms`); 148 + if (attempts < 3) { 149 + continue; 150 + } 139 151 140 - await new Promise((resolve) => setTimeout(resolve, delta)); 152 + throw new ClientResponseError(response); 153 + } catch (err) { 154 + // Network errors, etc 155 + if (attempts < 3) { 156 + continue; 141 157 } 142 - } else { 143 - using _progress = logger.progress(`Ratelimited, waiting`); 144 158 145 - await new Promise((resolve) => setTimeout(resolve, 60 * 1_000)); 159 + throw err; 146 160 } 147 161 } 148 162 } ··· 202 216 }; 203 217 204 218 export default Step4_Confirmation; 219 + 220 + const sleep = (ms: number): Promise<void> => { 221 + return new Promise((resolve) => setTimeout(resolve, ms)); 222 + };
+2 -2
src/views/bluesky/threadgate-applicator/steps/step5_finished.tsx
··· 1 - import { Stage, WizardStepProps } from '~/components/wizard'; 1 + import { Stage, type WizardStepProps } from '~/components/wizard'; 2 2 3 - import { ThreadgateApplicatorConstraints } from '../page'; 3 + import type { ThreadgateApplicatorConstraints } from '../page'; 4 4 5 5 export const Step5_Finished = ({}: WizardStepProps<ThreadgateApplicatorConstraints, 'Step5_Finished'>) => { 6 6 return (
+1 -1
src/views/bluesky/threadgate-applicator/utils.ts
··· 1 - import { ThreadgateState } from './page'; 1 + import type { ThreadgateState } from './page'; 2 2 3 3 const collator = new Intl.Collator('en'); 4 4
+3 -6
src/views/crypto/crypto-generate.tsx
··· 6 6 7 7 import Button from '~/components/inputs/button'; 8 8 import RadioInput from '~/components/inputs/radio-input'; 9 + import PageHeader from '~/components/page-header'; 9 10 10 11 type KeyType = 'p256' | 'secp256k1'; 11 12 ··· 26 27 27 28 return ( 28 29 <> 29 - <div class="p-4"> 30 - <h1 class="text-lg font-bold text-purple-800">Generate secret keys</h1> 31 - <p class="text-gray-600">Create a new secp256k1/nistp256 keypair</p> 32 - </div> 33 - <hr class="mx-4 border-gray-300" /> 30 + <PageHeader title="Generate secret keys" subtitle="Create a new secp256k1/nistp256 keypair" /> 34 31 35 32 <form 36 33 onSubmit={async (ev) => { ··· 54 51 ]); 55 52 56 53 const result: KeypairResult = { 57 - type: keypair.type, 54 + type: keypair.type as KeyType, 58 55 publicDidKey, 59 56 privateHex, 60 57 privateMultikey,
+255
src/views/crypto/crypto-info.tsx
··· 1 + import { createMemo, createSignal, Match, Show, Switch } from 'solid-js'; 2 + 3 + import { 4 + type DidKeyString, 5 + P256PrivateKeyExportable, 6 + P256PublicKey, 7 + parseDidKey, 8 + parsePrivateMultikey, 9 + parsePublicMultikey, 10 + Secp256k1PrivateKeyExportable, 11 + Secp256k1PublicKey, 12 + } from '@atcute/crypto'; 13 + import { fromBase16 } from '@atcute/multibase'; 14 + 15 + import { useTitle } from '~/lib/navigation/router'; 16 + 17 + import Button from '~/components/inputs/button'; 18 + import RadioInput from '~/components/inputs/radio-input'; 19 + import TextInput from '~/components/inputs/text-input'; 20 + import PageHeader from '~/components/page-header'; 21 + 22 + type KeyType = 'p256' | 'secp256k1'; 23 + type KeyFormat = 'did:key' | 'multikey' | 'hex'; 24 + 25 + interface KeyInfo { 26 + keyType: KeyType; 27 + isPrivate: boolean; 28 + inputFormat: KeyFormat; 29 + publicDidKey: DidKeyString; 30 + publicMultikey: string; 31 + privateHex?: string; 32 + privateMultikey?: string; 33 + } 34 + 35 + const DID_KEY_REGEX = /^did:key:z[a-km-zA-HJ-NP-Z1-9]+$/; 36 + const MULTIKEY_REGEX = /^z[a-km-zA-HJ-NP-Z1-9]+$/; 37 + const HEX_REGEX = /^[0-9a-f]+$/; 38 + 39 + const CryptoInfoPage = () => { 40 + const [input, setInput] = createSignal(''); 41 + const [hexKeyType, setHexKeyType] = createSignal<KeyType>(); 42 + const [result, setResult] = createSignal<KeyInfo>(); 43 + const [error, setError] = createSignal<string>(); 44 + 45 + const detectedFormat = createMemo((): KeyFormat | undefined => { 46 + const $input = input().trim(); 47 + 48 + if (DID_KEY_REGEX.test($input)) { 49 + return 'did:key'; 50 + } 51 + if (MULTIKEY_REGEX.test($input)) { 52 + return 'multikey'; 53 + } 54 + if (HEX_REGEX.test($input)) { 55 + return 'hex'; 56 + } 57 + }); 58 + 59 + const canSubmit = createMemo(() => { 60 + const format = detectedFormat(); 61 + if (!format) { 62 + return false; 63 + } 64 + if (format === 'hex' && !hexKeyType()) { 65 + return false; 66 + } 67 + return true; 68 + }); 69 + 70 + useTitle(() => `View crypto key info โ€” boat`); 71 + 72 + return ( 73 + <> 74 + <PageHeader title="View crypto key info" subtitle="Show basic metadata about a public or private key" /> 75 + 76 + <form 77 + onSubmit={async (ev) => { 78 + ev.preventDefault(); 79 + 80 + const $input = input().trim(); 81 + const format = detectedFormat(); 82 + 83 + setResult(); 84 + setError(); 85 + 86 + try { 87 + let info: KeyInfo; 88 + 89 + if (format === 'did:key') { 90 + const parsed = parseDidKey($input); 91 + const pubKey = 92 + parsed.type === 'p256' 93 + ? await P256PublicKey.importRaw(parsed.publicKeyBytes) 94 + : await Secp256k1PublicKey.importRaw(parsed.publicKeyBytes); 95 + 96 + info = { 97 + keyType: parsed.type, 98 + isPrivate: false, 99 + inputFormat: 'did:key', 100 + publicDidKey: await pubKey.exportPublicKey('did'), 101 + publicMultikey: await pubKey.exportPublicKey('multikey'), 102 + }; 103 + } else if (format === 'multikey') { 104 + // try parsing as private key first 105 + try { 106 + const parsed = parsePrivateMultikey($input); 107 + const privKey = 108 + parsed.type === 'p256' 109 + ? await P256PrivateKeyExportable.importRaw(parsed.privateKeyBytes) 110 + : await Secp256k1PrivateKeyExportable.importRaw(parsed.privateKeyBytes); 111 + 112 + info = { 113 + keyType: parsed.type, 114 + isPrivate: true, 115 + inputFormat: 'multikey', 116 + publicDidKey: await privKey.exportPublicKey('did'), 117 + publicMultikey: await privKey.exportPublicKey('multikey'), 118 + privateHex: await privKey.exportPrivateKey('rawHex'), 119 + privateMultikey: await privKey.exportPrivateKey('multikey'), 120 + }; 121 + } catch { 122 + // try parsing as public key 123 + const parsed = parsePublicMultikey($input); 124 + const pubKey = 125 + parsed.type === 'p256' 126 + ? await P256PublicKey.importRaw(parsed.publicKeyBytes) 127 + : await Secp256k1PublicKey.importRaw(parsed.publicKeyBytes); 128 + 129 + info = { 130 + keyType: parsed.type, 131 + isPrivate: false, 132 + inputFormat: 'multikey', 133 + publicDidKey: await pubKey.exportPublicKey('did'), 134 + publicMultikey: await pubKey.exportPublicKey('multikey'), 135 + }; 136 + } 137 + } else if (format === 'hex') { 138 + const keyType = hexKeyType()!; 139 + const privateKeyBytes = fromBase16($input); 140 + 141 + const privKey = 142 + keyType === 'p256' 143 + ? await P256PrivateKeyExportable.importRaw(privateKeyBytes) 144 + : await Secp256k1PrivateKeyExportable.importRaw(privateKeyBytes); 145 + 146 + info = { 147 + keyType: keyType, 148 + isPrivate: true, 149 + inputFormat: 'hex', 150 + publicDidKey: await privKey.exportPublicKey('did'), 151 + publicMultikey: await privKey.exportPublicKey('multikey'), 152 + privateHex: await privKey.exportPrivateKey('rawHex'), 153 + privateMultikey: await privKey.exportPrivateKey('multikey'), 154 + }; 155 + } else { 156 + throw new Error('Unknown key format'); 157 + } 158 + 159 + setResult(info); 160 + } catch (err) { 161 + console.error(err); 162 + setError(`Failed to parse key: ${err}`); 163 + } 164 + }} 165 + class="flex flex-col gap-4 p-4" 166 + > 167 + <TextInput 168 + label="Public or private key" 169 + blurb="Accepts did:key, multikey, or hex format" 170 + monospace 171 + autocomplete="off" 172 + autocorrect="off" 173 + placeholder="did:key:z... or z... or a5973930f9d348..." 174 + value={input()} 175 + required 176 + onChange={setInput} 177 + /> 178 + 179 + <Show when={detectedFormat() === 'hex'}> 180 + <RadioInput 181 + label="This is a..." 182 + value={hexKeyType()} 183 + required 184 + options={[ 185 + { value: 'secp256k1', label: `ES256K (secp256k1) private key` }, 186 + { value: 'p256', label: `ES256 (p256) private key` }, 187 + ]} 188 + onChange={setHexKeyType} 189 + /> 190 + </Show> 191 + 192 + <div> 193 + <Button type="submit" disabled={!canSubmit()}> 194 + Inspect 195 + </Button> 196 + </div> 197 + </form> 198 + 199 + <hr class="mx-4 border-gray-300" /> 200 + 201 + <Switch> 202 + <Match when={error()}> 203 + <div class="p-4 text-red-600">{error()}</div> 204 + </Match> 205 + 206 + <Match when={result()} keyed> 207 + {(info) => ( 208 + <div class="flex flex-col gap-6 break-words p-4 text-gray-900"> 209 + <div> 210 + <p class="font-semibold text-gray-600">Key type</p> 211 + <span> 212 + {/* @once */ info.keyType === 'p256' 213 + ? 'ES256 (p256)' 214 + : 'ES256K (secp256k1)'}{' '} 215 + {/* @once */ info.isPrivate ? 'private' : 'public'} key 216 + </span> 217 + </div> 218 + 219 + <div> 220 + <p class="font-semibold text-gray-600">Input encoding</p> 221 + <span>{/* @once */ info.inputFormat}</span> 222 + </div> 223 + 224 + <div> 225 + <p class="font-semibold text-gray-600">Public key (did:key)</p> 226 + <span class="font-mono">{/* @once */ info.publicDidKey}</span> 227 + </div> 228 + 229 + <div> 230 + <p class="font-semibold text-gray-600">Public key (multikey)</p> 231 + <span class="font-mono">{/* @once */ info.publicMultikey}</span> 232 + </div> 233 + 234 + <Show when={info.privateHex}> 235 + <div> 236 + <p class="font-semibold text-gray-600">Private key (hex)</p> 237 + <span class="font-mono">{/* @once */ info.privateHex}</span> 238 + </div> 239 + </Show> 240 + 241 + <Show when={info.privateMultikey}> 242 + <div> 243 + <p class="font-semibold text-gray-600">Private key (multikey)</p> 244 + <span class="font-mono">{/* @once */ info.privateMultikey}</span> 245 + </div> 246 + </Show> 247 + </div> 248 + )} 249 + </Match> 250 + </Switch> 251 + </> 252 + ); 253 + }; 254 + 255 + export default CryptoInfoPage;
+6 -8
src/views/frontpage.tsx
··· 1 - import { Component, ComponentProps } from 'solid-js'; 1 + import type { Component, ComponentProps } from 'solid-js'; 2 2 3 3 import { useTitle } from '~/lib/navigation/router'; 4 + 5 + import PageHeader from '~/components/page-header'; 4 6 5 7 import HistoryIcon from '~/components/ic-icons/baseline-history'; 6 8 import KeyIcon from '~/components/ic-icons/baseline-key'; ··· 102 104 { 103 105 name: `Migrate account`, 104 106 description: `Move your account data to another server`, 105 - href: null, 107 + href: '/account-migrate', 106 108 icon: MoveUpOutlinedIcon, 107 109 }, 108 110 ], ··· 119 121 { 120 122 name: `View crypto key info`, 121 123 description: `Show basic metadata about a public or private key`, 122 - href: null, 124 + href: `/crypto-info`, 123 125 icon: KeyVisualizerIcon, 124 126 }, 125 127 ], ··· 170 172 171 173 return ( 172 174 <> 173 - <div class="p-4"> 174 - <h1 class="text-lg font-bold text-purple-800">boat</h1> 175 - <p class="text-gray-600">handy online tools for AT Protocol</p> 176 - </div> 177 - <hr class="mx-4 border-gray-300" /> 175 + <PageHeader title="boat" subtitle="handy online tools for AT Protocol" /> 178 176 179 177 <div class="flex grow flex-col pb-2">{nodes}</div> 180 178
+20 -32
src/views/identity/did-lookup.tsx
··· 1 1 import { Match, Switch } from 'solid-js'; 2 2 3 - import { isAtprotoDid, isHandle, type AtprotoDid, type Did, type Handle } from '@atcute/identity'; 3 + import { isAtprotoDid } from '@atcute/identity'; 4 + import { isHandle, type AtprotoDid, type Did, type Handle } from '@atcute/lexicons/syntax'; 4 5 5 6 import { getDidDocument } from '~/api/queries/did-doc'; 6 7 import { resolveHandleViaAppView } from '~/api/queries/handle'; 7 8 import { isServiceUrlString } from '~/api/types/strings'; 8 - import { DID_OR_HANDLE_RE } from '~/api/utils/strings'; 9 9 10 10 import { useTitle } from '~/lib/navigation/router'; 11 11 import { createQuery } from '~/lib/utils/query'; ··· 15 15 import ErrorView from '~/components/error-view'; 16 16 import Button from '~/components/inputs/button'; 17 17 import TextInput from '~/components/inputs/text-input'; 18 + import PageHeader from '~/components/page-header'; 18 19 19 20 const DidLookupPage = () => { 20 21 const [params, setParams] = useSearchParams({ ··· 46 47 47 48 return ( 48 49 <> 49 - <div class="p-4"> 50 - <h1 class="text-lg font-bold text-purple-800">View identity info</h1> 51 - <p class="text-gray-600">Look up an account's DID document</p> 52 - </div> 53 - <hr class="mx-4 border-gray-300" /> 50 + <PageHeader title="View identity info" subtitle="Look up an account's DID document" /> 54 51 55 52 <form 56 53 onSubmit={(ev) => { ··· 67 64 type="text" 68 65 name="ident" 69 66 autocomplete="username" 70 - pattern={/* @once */ DID_OR_HANDLE_RE.source} 71 67 placeholder="paul.bsky.social" 72 68 autofocus 73 69 /> ··· 101 97 102 98 <div> 103 99 <p class="font-semibold text-gray-600">Identifies as</p> 104 - <ol class="list-disc pl-4">{doc.alsoKnownAs?.map((ident) => <li>{ident}</li>)}</ol> 100 + <ol class="list-disc pl-4"> 101 + {doc.alsoKnownAs?.map((ident) => ( 102 + <li>{ident}</li> 103 + ))} 104 + </ol> 105 105 </div> 106 106 107 107 <div> ··· 130 130 131 131 <div class="mt-2 flex flex-wrap gap-2 empty:hidden"> 132 132 {isPDS && isServiceUrl && ( 133 - <button 134 - disabled 135 - class="flex h-9 select-none items-center rounded border border-gray-300 px-4 text-sm font-semibold text-gray-800 hover:bg-gray-100 active:bg-gray-100 disabled:pointer-events-none disabled:opacity-50" 136 - > 133 + <Button variant="outline" disabled> 137 134 View PDS info 138 - </button> 135 + </Button> 139 136 )} 140 137 141 138 {isPDS && isServiceUrl && ( 142 - <button 143 - disabled 144 - class="flex h-9 select-none items-center rounded border border-gray-300 px-4 text-sm font-semibold text-gray-800 hover:bg-gray-100 active:bg-gray-100 disabled:pointer-events-none disabled:opacity-50" 145 - > 139 + <Button variant="outline" disabled> 146 140 Explore account repository 147 - </button> 141 + </Button> 148 142 )} 149 143 150 144 {isLabeler && isServiceUrl && ( 151 - <button 152 - disabled 153 - class="flex h-9 select-none items-center rounded border border-gray-300 px-4 text-sm font-semibold text-gray-800 hover:bg-gray-100 active:bg-gray-100 disabled:pointer-events-none disabled:opacity-50" 154 - > 145 + <Button variant="outline" disabled> 155 146 View emitted labels 156 - </button> 147 + </Button> 157 148 )} 158 149 </div> 159 150 </li> ··· 182 173 </div> 183 174 184 175 <div class="flex flex-wrap gap-4 p-4 pt-2"> 185 - <button 176 + <Button 177 + variant="outline" 186 178 onClick={() => { 187 179 navigator.clipboard.writeText(JSON.stringify(doc, null, 2)); 188 180 }} 189 - class="flex h-9 select-none items-center rounded border border-gray-300 px-4 text-sm font-semibold text-gray-800 hover:bg-gray-100 active:bg-gray-100" 190 181 > 191 182 Copy DID document 192 - </button> 183 + </Button> 193 184 194 185 {isDidPlc && ( 195 - <a 196 - href={`/plc-oplogs?q=${params.q!}`} 197 - class="flex h-9 select-none items-center rounded border border-gray-300 px-4 text-sm font-semibold text-gray-800 hover:bg-gray-100 active:bg-gray-100" 198 - > 186 + <Button variant="outline" href={`/plc-oplogs?q=${params.q!}`}> 199 187 View PLC operation logs 200 - </a> 188 + </Button> 201 189 )} 202 190 </div> 203 191 </>
+7 -9
src/views/identity/plc-applicator/page.tsx
··· 1 1 import { createEffect, createSignal, onCleanup } from 'solid-js'; 2 2 3 + import type { ComAtprotoIdentityGetRecommendedDidCredentials } from '@atcute/atproto'; 3 4 import type { CredentialManager } from '@atcute/client'; 4 - import type { ComAtprotoIdentityGetRecommendedDidCredentials } from '@atcute/client/lexicons'; 5 5 import type { P256PrivateKey, Secp256k1PrivateKey } from '@atcute/crypto'; 6 6 import type { CompatibleOperation, IndexedEntry, IndexedEntryWithSigner } from '@atcute/did-plc'; 7 - import type { Did, DidDocument } from '@atcute/identity'; 7 + import type { DidDocument } from '@atcute/identity'; 8 + import type { Did } from '@atcute/lexicons/syntax'; 8 9 9 - import { UpdatePayload } from '~/api/types/plc'; 10 + import type { UpdatePayload } from '~/api/types/plc'; 10 11 11 12 import { history } from '~/globals/navigation'; 12 13 13 14 import { useTitle } from '~/lib/navigation/router'; 14 15 16 + import PageHeader from '~/components/page-header'; 15 17 import { Wizard } from '~/components/wizard'; 16 18 17 19 import Step1_HandleInput from './steps/step1_handle-input'; ··· 31 33 export interface PdsSigningMethod { 32 34 type: 'pds'; 33 35 manager: CredentialManager; 34 - recommendedDidDoc: ComAtprotoIdentityGetRecommendedDidCredentials.Output; 36 + recommendedDidDoc: ComAtprotoIdentityGetRecommendedDidCredentials.$output; 35 37 } 36 38 37 39 export type Keypair = P256PrivateKey | Secp256k1PrivateKey; ··· 100 102 101 103 return ( 102 104 <> 103 - <div class="p-4"> 104 - <h1 class="text-lg font-bold text-purple-800">Apply PLC operations</h1> 105 - <p class="text-gray-600">Submit operations to your did:plc identity</p> 106 - </div> 107 - <hr class="mx-4 border-gray-300" /> 105 + <PageHeader title="Apply PLC operations" subtitle="Submit operations to your did:plc identity" /> 108 106 109 107 <Wizard<PlcApplicatorConstraints> 110 108 initialStep="Step1_HandleInput"
+1 -1
src/views/identity/plc-applicator/plc-utils.ts
··· 1 1 import type { IndexedEntry } from '@atcute/did-plc'; 2 2 3 - import { UpdatePayload } from '~/api/types/plc'; 3 + import type { UpdatePayload } from '~/api/types/plc'; 4 4 5 5 import { assert } from '~/lib/utils/invariant'; 6 6
+13 -12
src/views/identity/plc-applicator/steps/step1_handle-input.tsx
··· 1 1 import { createSignal } from 'solid-js'; 2 2 3 - import { XRPCError } from '@atcute/client'; 3 + import { ClientResponseError } from '@atcute/client'; 4 4 import { processIndexedEntryLog } from '@atcute/did-plc'; 5 - import { type Did, isHandle, isPlcDid } from '@atcute/identity'; 5 + import { isPlcDid } from '@atcute/identity'; 6 + import { isHandle, type Did } from '@atcute/lexicons/syntax'; 6 7 7 8 import { getDidDocument } from '~/api/queries/did-doc'; 8 9 import { resolveHandleViaAppView } from '~/api/queries/handle'; 9 10 import { getPlcAuditLogs } from '~/api/queries/plc'; 10 - import { DID_OR_HANDLE_RE } from '~/api/utils/strings'; 11 11 12 12 import { createMutation } from '~/lib/utils/mutation'; 13 13 14 14 import Button from '~/components/inputs/button'; 15 15 import RadioInput from '~/components/inputs/radio-input'; 16 16 import TextInput from '~/components/inputs/text-input'; 17 - import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard'; 17 + import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard'; 18 18 19 - import { type PlcInformation, PlcApplicatorConstraints } from '../page'; 19 + import type { PlcApplicatorConstraints, PlcInformation } from '../page'; 20 20 21 21 type Method = 'pds' | 'key'; 22 22 ··· 70 70 onNext('Step2_PrivateKeyInput', { info }); 71 71 } 72 72 }, 73 - onError(error) { 73 + onError(err) { 74 74 let message: string | undefined; 75 75 76 - if (error instanceof XRPCError) { 77 - if (error.kind === 'InvalidRequest' && error.message.includes('resolve handle')) { 76 + if (err instanceof ClientResponseError) { 77 + if (err.error === 'InvalidRequest' && err.description?.includes('resolve handle')) { 78 78 message = `Can't seem to resolve handle, is it typed correctly?`; 79 79 } 80 - } else if (error instanceof DidIsNotPlcError) { 81 - message = error.message; 80 + } else if (err instanceof DidIsNotPlcError) { 81 + message = err.message; 82 82 } 83 83 84 84 if (message !== undefined) { 85 85 setError(message); 86 86 } else { 87 - setError(`Something went wrong: ${error}`); 87 + console.error(err); 88 + 89 + setError(`Something went wrong: ${err}`); 88 90 } 89 91 }, 90 92 }); ··· 105 107 placeholder="paul.bsky.social" 106 108 value={identifier()} 107 109 required 108 - pattern={/* @once */ DID_OR_HANDLE_RE.source} 109 110 autofocus={isActive()} 110 111 onChange={setIdentifier} 111 112 />
+16 -19
src/views/identity/plc-applicator/steps/step2_pds-authentication.tsx
··· 1 1 import { createSignal, Match, Show, Switch } from 'solid-js'; 2 2 3 - import { AtpAccessJwt, CredentialManager, XRPC, XRPCError } from '@atcute/client'; 4 - import { decodeJwt } from '@atcute/client/utils/jwt'; 3 + import { type AtpAccessJwt, Client, ClientResponseError, CredentialManager, ok } from '@atcute/client'; 5 4 import { getPdsEndpoint } from '@atcute/identity'; 6 5 7 6 import { formatTotpCode, TOTP_RE } from '~/api/utils/auth'; 7 + import { decodeJwt } from '~/api/utils/jwt'; 8 8 9 9 import { createMutation } from '~/lib/utils/mutation'; 10 10 11 11 import Button from '~/components/inputs/button'; 12 12 import TextInput from '~/components/inputs/text-input'; 13 - import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard'; 13 + import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard'; 14 14 15 - import { PlcApplicatorConstraints } from '../page'; 15 + import type { PlcApplicatorConstraints } from '../page'; 16 16 17 17 class InsufficientLoginError extends Error {} 18 18 ··· 59 59 setPassword(''); 60 60 setIsTotpRequired(false); 61 61 }, 62 - onError(error) { 62 + onError(err) { 63 63 let message: string | undefined; 64 64 65 - if (error instanceof XRPCError) { 66 - if (error.kind === 'AuthFactorTokenRequired') { 65 + if (err instanceof ClientResponseError) { 66 + if (err.error === 'AuthFactorTokenRequired') { 67 67 setOtp(''); 68 68 setIsTotpRequired(true); 69 69 return; 70 70 } 71 71 72 - if (error.kind === 'AuthenticationRequired') { 72 + if (err.error === 'AuthenticationRequired') { 73 73 message = `Invalid identifier or password`; 74 - } else if (error.kind === 'AccountTakedown') { 74 + } else if (err.error === 'AccountTakedown') { 75 75 message = `Account has been taken down`; 76 - } else if (error.message.includes('Token is invalid')) { 76 + } else if (err.description?.includes('Token is invalid')) { 77 77 message = `Invalid one-time confirmation code`; 78 78 setIsTotpRequired(true); 79 79 } 80 - } else if (error instanceof InsufficientLoginError) { 81 - message = error.message; 80 + } else if (err instanceof InsufficientLoginError) { 81 + message = err.message; 82 82 } 83 83 84 84 if (message !== undefined) { 85 85 setError(message); 86 86 } else { 87 - console.error(error); 88 - setError(`Something went wrong: ${error}`); 87 + console.error(err); 88 + setError(`Something went wrong: ${err}`); 89 89 } 90 90 }, 91 91 }); 92 92 93 93 const dispatchMutation = createMutation({ 94 94 async mutationFn({ manager }: { manager: CredentialManager }) { 95 - const rpc = new XRPC({ handler: manager }); 96 - const { data: recommendedDidDoc } = await rpc.get( 97 - 'com.atproto.identity.getRecommendedDidCredentials', 98 - {}, 99 - ); 95 + const rpc = new Client({ handler: manager }); 96 + const recommendedDidDoc = await ok(rpc.get('com.atproto.identity.getRecommendedDidCredentials')); 100 97 101 98 return { recommendedDidDoc }; 102 99 },
+3 -9
src/views/identity/plc-applicator/steps/step2_private-key-input.tsx
··· 8 8 import Button from '~/components/inputs/button'; 9 9 import RadioInput from '~/components/inputs/radio-input'; 10 10 import TextInput from '~/components/inputs/text-input'; 11 - import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard'; 11 + import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard'; 12 12 13 13 import type { Keypair, PlcApplicatorConstraints, PrivateKeySigningMethod } from '../page'; 14 14 ··· 97 97 }); 98 98 }, 99 99 onError(error) { 100 - let message: string | undefined; 101 - 102 - if (message !== undefined) { 103 - setError(message); 104 - } else { 105 - console.error(error); 106 - setError(`Something went wrong: ${error}`); 107 - } 100 + console.error(error); 101 + setError(`Something went wrong: ${error}`); 108 102 }, 109 103 }); 110 104
+4 -4
src/views/identity/plc-applicator/steps/step3_operation-select.tsx
··· 7 7 type IndexedEntryWithSigner, 8 8 normalizeOp, 9 9 } from '@atcute/did-plc'; 10 - import type { Did } from '@atcute/identity'; 10 + import type { Did } from '@atcute/lexicons'; 11 11 12 12 import Button from '~/components/inputs/button'; 13 + import RadioInput from '~/components/inputs/radio-input'; 13 14 import SelectInput from '~/components/inputs/select-input'; 14 - import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard'; 15 + import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard'; 15 16 16 - import { PlcApplicatorConstraints } from '../page'; 17 - import RadioInput from '~/components/inputs/radio-input'; 17 + import type { PlcApplicatorConstraints } from '../page'; 18 18 19 19 const Step3_OperationSelect = ({ 20 20 data,
+2 -2
src/views/identity/plc-applicator/steps/step4_payload-input.tsx
··· 4 4 5 5 import Button from '~/components/inputs/button'; 6 6 import MultilineInput from '~/components/inputs/multiline-input'; 7 - import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard'; 7 + import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard'; 8 8 9 - import { PlcApplicatorConstraints } from '../page'; 9 + import type { PlcApplicatorConstraints } from '../page'; 10 10 import { getPlcPayload } from '../plc-utils'; 11 11 12 12 export const Step4_PayloadInput = ({
+30 -25
src/views/identity/plc-applicator/steps/step5_pds-confirmation.tsx
··· 1 1 import { createSignal } from 'solid-js'; 2 2 3 - import { XRPC, XRPCError } from '@atcute/client'; 3 + import { Client, ClientResponseError, ok } from '@atcute/client'; 4 4 5 5 import { formatTotpCode, TOTP_RE } from '~/api/utils/auth'; 6 6 ··· 9 9 import CheckIcon from '~/components/ic-icons/baseline-check'; 10 10 import Button from '~/components/inputs/button'; 11 11 import TextInput from '~/components/inputs/text-input'; 12 - import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard'; 12 + import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard'; 13 13 14 - import { PlcApplicatorConstraints } from '../page'; 14 + import type { PlcApplicatorConstraints } from '../page'; 15 15 16 16 export const Step5_PdsConfirmation = ({ 17 17 data, ··· 27 27 const requestMutation = createMutation({ 28 28 async mutationFn() { 29 29 const manager = data.method.manager; 30 - const rpc = new XRPC({ handler: manager }); 30 + const rpc = new Client({ handler: manager }); 31 31 32 - await rpc.call('com.atproto.identity.requestPlcOperationSignature', {}); 32 + await ok(rpc.post('com.atproto.identity.requestPlcOperationSignature', { as: null })); 33 33 }, 34 34 onMutate() { 35 35 setRequestError(); ··· 49 49 const applyMutation = createMutation({ 50 50 async mutationFn({ code }: { code: string }) { 51 51 const manager = data.method.manager; 52 - const rpc = new XRPC({ handler: manager }); 52 + const client = new Client({ handler: manager }); 53 53 54 54 const payload = data.payload; 55 55 56 - const { data: signage } = await rpc.call('com.atproto.identity.signPlcOperation', { 57 - data: { 58 - token: formatTotpCode(code), 59 - alsoKnownAs: payload.alsoKnownAs, 60 - rotationKeys: payload.rotationKeys, 61 - services: payload.services, 62 - verificationMethods: payload.verificationMethods, 63 - }, 64 - }); 56 + const signage = await ok( 57 + client.post('com.atproto.identity.signPlcOperation', { 58 + input: { 59 + token: formatTotpCode(code), 60 + alsoKnownAs: payload.alsoKnownAs, 61 + rotationKeys: payload.rotationKeys, 62 + services: payload.services, 63 + verificationMethods: payload.verificationMethods, 64 + }, 65 + }), 66 + ); 65 67 66 - await rpc.call('com.atproto.identity.submitPlcOperation', { 67 - data: { 68 - operation: signage.operation, 69 - }, 70 - }); 68 + await ok( 69 + client.post('com.atproto.identity.submitPlcOperation', { 70 + as: null, 71 + input: { 72 + operation: signage.operation, 73 + }, 74 + }), 75 + ); 71 76 }, 72 77 onMutate() { 73 78 setApplyError(); ··· 75 80 onSuccess() { 76 81 onNext('Step6_Finished', {}); 77 82 }, 78 - onError(error) { 83 + onError(err) { 79 84 let message: string | undefined; 80 85 81 - if (error instanceof XRPCError) { 82 - if (error.kind === 'InvalidToken' || error.kind === 'ExpiredToken') { 86 + if (err instanceof ClientResponseError) { 87 + if (err.error === 'InvalidToken' || err.error === 'ExpiredToken') { 83 88 message = `Confirmation code has expired`; 84 89 } 85 90 } ··· 87 92 if (message !== undefined) { 88 93 setApplyError(message); 89 94 } else { 90 - console.error(error); 91 - setApplyError(`Something went wrong: ${error}`); 95 + console.error(err); 96 + setApplyError(`Something went wrong: ${err}`); 92 97 } 93 98 }, 94 99 });
+5 -6
src/views/identity/plc-applicator/steps/step5_private-key-confirmation.tsx
··· 9 9 10 10 import Button from '~/components/inputs/button'; 11 11 import TextInput from '~/components/inputs/text-input'; 12 - import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard'; 12 + import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard'; 13 13 14 - import { PlcApplicatorConstraints } from '../page'; 14 + import type { PlcApplicatorConstraints } from '../page'; 15 15 16 16 const Step5_PrivateKeyConfirmation = ({ 17 17 data, ··· 78 78 }} 79 79 > 80 80 <p class="text-pretty"> 81 - To continue with this submission, type in the following code{' '} 82 - <code class="whitespace-nowrap font-bold">{code}</code> to the confirmation box below. 81 + To continue with this submission, type in <code class="whitespace-nowrap font-bold">{code}</code> to 82 + the confirmation box below. 83 83 </p> 84 84 85 85 <TextInput 86 - label="Confirmation code" 86 + label="Confirmation" 87 87 type="text" 88 88 autocomplete="one-time-code" 89 89 autocorrect="off" 90 90 required 91 91 pattern={code} 92 - placeholder="AAAAA-BBBBB" 93 92 autofocus={isActive()} 94 93 monospace 95 94 />
+2 -2
src/views/identity/plc-applicator/steps/step6_finished.tsx
··· 1 - import { Stage, WizardStepProps } from '~/components/wizard'; 1 + import { Stage, type WizardStepProps } from '~/components/wizard'; 2 2 3 - import { PlcApplicatorConstraints } from '../page'; 3 + import type { PlcApplicatorConstraints } from '../page'; 4 4 5 5 export const Step6_Finished = ({}: WizardStepProps<PlcApplicatorConstraints, 'Step6_Finished'>) => { 6 6 return (
+5 -9
src/views/identity/plc-oplogs.tsx
··· 1 - import { createSignal, JSX, Match, onCleanup, Switch } from 'solid-js'; 1 + import { createSignal, Match, onCleanup, Switch, type JSX } from 'solid-js'; 2 2 3 3 import type { IndexedEntry, Service } from '@atcute/did-plc'; 4 - import { type Did, type Handle, isHandle, isPlcDid } from '@atcute/identity'; 4 + import { isPlcDid } from '@atcute/identity'; 5 + import { isHandle, type Did, type Handle } from '@atcute/lexicons/syntax'; 5 6 6 7 import { resolveHandleViaAppView } from '~/api/queries/handle'; 7 - import { DID_OR_HANDLE_RE } from '~/api/utils/strings'; 8 8 9 9 import { getPlcAuditLogs } from '~/api/queries/plc'; 10 10 import { useTitle } from '~/lib/navigation/router'; ··· 20 20 import ContentCopyIcon from '~/components/ic-icons/baseline-content-copy'; 21 21 import Button from '~/components/inputs/button'; 22 22 import TextInput from '~/components/inputs/text-input'; 23 + import PageHeader from '~/components/page-header'; 23 24 24 25 const PlcOperationLogPage = () => { 25 26 const [params, setParams] = useSearchParams({ ··· 55 56 56 57 return ( 57 58 <> 58 - <div class="p-4"> 59 - <h1 class="text-lg font-bold text-purple-800">View PLC operation logs</h1> 60 - <p class="text-gray-600">Show history of a did:plc identity</p> 61 - </div> 62 - <hr class="mx-4 border-gray-300" /> 59 + <PageHeader title="View PLC operation logs" subtitle="Show history of a did:plc identity" /> 63 60 64 61 <form 65 62 onSubmit={(ev) => { ··· 76 73 type="text" 77 74 name="ident" 78 75 autocomplete="username" 79 - pattern={/* @once */ DID_OR_HANDLE_RE.source} 80 76 placeholder="paul.bsky.social" 81 77 autofocus 82 78 />
+9 -6
src/views/repository/repo-archive-explore/page.tsx
··· 1 1 import { Match, Switch } from 'solid-js'; 2 2 3 - import { iterateAtpRepo } from '@atcute/car'; 3 + import { fromStream } from '@atcute/repo'; 4 4 5 5 import { createMutation } from '~/lib/utils/mutation'; 6 6 7 - import WelcomeView from './views/welcome'; 8 - 9 - import { Archive, RecordEntry } from './types'; 7 + import type { Archive, RecordEntry } from './types'; 10 8 import ExploreView from './views/explore'; 9 + import WelcomeView from './views/welcome'; 11 10 12 11 const ArchiveExplorePage = () => { 13 12 const mutation = createMutation({ 14 13 async mutationFn({ file }: { file: File }): Promise<Archive> { 15 - const buffer = new Uint8Array(await file.arrayBuffer()); 14 + const stream = file.stream(); 15 + await using repo = fromStream(stream); 16 16 17 17 const collections = new Map<string, RecordEntry[]>(); 18 18 const archive: Archive = { ··· 20 20 entries: [], 21 21 }; 22 22 23 - for (const entry of iterateAtpRepo(buffer)) { 23 + for await (const entry of repo) { 24 24 let list = collections.get(entry.collection); 25 25 if (list === undefined) { 26 26 collections.set(entry.collection, (list = [])); ··· 41 41 } 42 42 43 43 return archive; 44 + }, 45 + onError(err) { 46 + console.error(err); 44 47 }, 45 48 }); 46 49
+1 -1
src/views/repository/repo-archive-explore/views/explore/record.tsx
··· 4 4 5 5 import { createQuery } from '~/lib/utils/query'; 6 6 7 - import { Archive, RecordEntry } from '../../types'; 7 + import type { Archive, RecordEntry } from '../../types'; 8 8 9 9 interface RecordSubviewProps { 10 10 archive: Archive;
+9 -42
src/views/repository/repo-archive-explore/views/welcome.tsx
··· 3 3 import type { MutationReturn } from '~/lib/utils/mutation'; 4 4 5 5 import CircularProgress from '~/components/circular-progress'; 6 + import FileDropZone from '~/components/file-drop-zone'; 7 + import PageHeader from '~/components/page-header'; 6 8 7 - import { Archive } from '../types'; 8 - import { createDropZone } from '~/lib/hooks/dropzone'; 9 + import type { Archive } from '../types'; 9 10 10 11 interface WelcomeViewProps { 11 12 mutation: MutationReturn<Archive, { file: File }>; 12 13 } 13 14 14 15 const WelcomeView = ({ mutation }: WelcomeViewProps) => { 15 - const { ref: dropRef, isDropping } = createDropZone({ 16 - // Checked, the mime type for CAR files is blank. 17 - dataTypes: [''], 18 - multiple: false, 19 - onDrop(files) { 20 - if (files) { 21 - mutation.mutate({ file: files[0] }); 22 - } 23 - }, 24 - }); 25 - 26 16 return ( 27 17 <> 28 - <div class="p-4"> 29 - <h1 class="text-lg font-bold text-purple-800">Explore archive</h1> 30 - <p class="text-gray-600">Explore a repository archive</p> 31 - </div> 32 - <hr class="mx-4 border-gray-300" /> 18 + <PageHeader title="Explore archive" subtitle="Explore a repository archive" /> 33 19 34 20 <div class="flex flex-col gap-4 p-4"> 35 - <fieldset 36 - ref={dropRef} 37 - class={ 38 - `grid place-items-center rounded border border-gray-300 px-6 py-12 disabled:opacity-50` + 39 - (!isDropping() ? ` bg-gray-100` : ` bg-green-100`) 40 - } 21 + <FileDropZone 22 + accept=".car,application/vnd.ipld.car" 23 + dataTypes={['']} 24 + onFiles={(files) => mutation.mutate({ file: files[0] })} 41 25 > 42 - <div class="flex flex-col items-center gap-4"> 43 - <button 44 - onClick={() => { 45 - const input = document.createElement('input'); 46 - input.type = 'file'; 47 - input.accept = '.car,application/vnd.ipld.car'; 48 - input.oninput = () => mutation.mutate({ file: input.files![0] }); 49 - 50 - input.click(); 51 - }} 52 - class="flex h-9 select-none items-center rounded border border-gray-400 px-4 text-sm font-semibold text-gray-800 hover:bg-gray-200 active:bg-gray-200 disabled:pointer-events-none" 53 - > 54 - Browse files 55 - </button> 56 - <p class="select-none font-medium text-gray-600">or drop your file here</p> 57 - </div> 58 - 59 26 <div 60 27 hidden={!mutation.isPending} 61 28 class="absolute inset-0 flex flex-col items-center justify-center gap-3 bg-gray-50" ··· 63 30 <CircularProgress /> 64 31 <span class="font-medium">Reading CAR file</span> 65 32 </div> 66 - </fieldset> 33 + </FileDropZone> 67 34 68 35 <Show when={mutation.error}> 69 36 <p class="whitespace-pre-wrap text-[0.8125rem] font-medium leading-5 text-red-800">
+23 -62
src/views/repository/repo-archive-unpack.tsx
··· 1 1 import { FileSystemWritableFileStream, showSaveFilePicker } from 'native-file-system-adapter'; 2 2 import { createSignal } from 'solid-js'; 3 3 4 - import { iterateAtpRepo } from '@atcute/car'; 4 + import { fromStream } from '@atcute/repo'; 5 5 import { writeTarEntry } from '@mary/tar'; 6 6 7 - import { createDropZone } from '~/lib/hooks/dropzone'; 8 7 import { useTitle } from '~/lib/navigation/router'; 9 8 import { makeAbortable } from '~/lib/utils/abortable'; 10 9 10 + import FileDropZone from '~/components/file-drop-zone'; 11 11 import Logger, { createLogger } from '~/components/logger'; 12 + import PageHeader from '~/components/page-header'; 12 13 13 14 // @ts-expect-error: new API 14 15 const yieldToScheduler: () => Promise<void> = window?.scheduler?.yield ··· 16 17 window.scheduler.yield.bind(window.scheduler) 17 18 : undefined; 18 19 19 - const yieldToIdle = 20 - typeof requestIdleCallback === 'function' 21 - ? () => new Promise((resolve) => requestIdleCallback(resolve)) 22 - : () => new Promise((resolve) => setTimeout(resolve, 1)); 23 - 24 20 const UnpackCarPage = () => { 25 21 const logger = createLogger(); 26 22 27 23 const [getSignal, cleanup] = makeAbortable(); 28 24 const [pending, setPending] = createSignal(false); 29 25 30 - const { ref: dropRef, isDropping } = createDropZone({ 31 - // Checked, the mime type for CAR files is blank. 32 - dataTypes: [''], 33 - multiple: false, 34 - onDrop(files) { 35 - if (files) { 36 - onFileDrop(files); 37 - } 38 - }, 39 - }); 40 - 41 26 const mutate = async (file: File, signal: AbortSignal) => { 42 - logger.info(`Starting extraction for ${file.name}`); 27 + logger.log(`Starting extraction`); 43 28 44 - const buf = await file.arrayBuffer(); 45 - const ui8 = new Uint8Array(buf); 29 + const stream = file.stream(); 30 + await using repo = fromStream(stream); 46 31 47 - let currentCollection: string | undefined; 48 32 let count = 0; 49 33 50 34 let writable: FileSystemWritableFileStream | undefined; 51 35 52 - for (const { collection, rkey, record } of iterateAtpRepo(ui8)) { 36 + using progress = logger.progress(`Unpacking records (${count} entries)`); 37 + 38 + for await (const { collection, rkey, record } of repo) { 53 39 if (writable === undefined) { 54 40 using _progress = logger.progress(`Waiting for the user`); 55 41 ··· 87 73 88 74 signal.throwIfAborted(); 89 75 90 - if (currentCollection !== collection) { 91 - logger.log(`Current progress: ${collection}`); 92 - currentCollection = collection; 93 - 94 - if (yieldToScheduler === undefined) { 95 - await yieldToIdle(); 96 - } 97 - } 98 - 99 76 const entry = writeTarEntry({ 100 77 filename: `${collection}/${filenamify(rkey)}.json`, 101 78 data: JSON.stringify(record, null, 2), 102 79 }); 103 80 104 - writable.write(entry); 105 81 count++; 106 82 83 + if (count % 100 !== 0) { 84 + writable.write(entry); 85 + } else { 86 + await writable.write(entry); 87 + } 88 + 89 + progress.update(`Unpacking records (${count} entries)`); 90 + 107 91 if (yieldToScheduler !== undefined) { 108 92 await yieldToScheduler(); 109 93 } ··· 161 145 162 146 return ( 163 147 <> 164 - <div class="p-4"> 165 - <h1 class="text-lg font-bold text-purple-800">Unpack archive</h1> 166 - <p class="text-gray-600">Extract a repository archive into a tarball</p> 167 - </div> 168 - <hr class="mx-4 border-gray-300" /> 148 + <PageHeader title="Unpack archive" subtitle="Extract a repository archive into a tarball" /> 169 149 170 150 <div class="p-4"> 171 - <fieldset 172 - ref={dropRef} 151 + <FileDropZone 152 + accept=".car,application/vnd.ipld.car" 153 + dataTypes={['']} 173 154 disabled={pending()} 174 - class={ 175 - `grid place-items-center rounded border border-gray-300 px-6 py-12 disabled:opacity-50` + 176 - (pending() || !isDropping() ? ` bg-gray-100` : ` bg-green-100`) 177 - } 178 - > 179 - <div class="flex flex-col items-center gap-4"> 180 - <button 181 - onClick={() => { 182 - const input = document.createElement('input'); 183 - input.type = 'file'; 184 - input.accept = '.car,application/vnd.ipld.car'; 185 - input.oninput = () => onFileDrop(Array.from(input.files!)); 186 - 187 - input.click(); 188 - }} 189 - class="flex h-9 select-none items-center rounded border border-gray-400 px-4 text-sm font-semibold text-gray-800 hover:bg-gray-200 active:bg-gray-200 disabled:pointer-events-none" 190 - > 191 - Browse files 192 - </button> 193 - <p class="select-none font-medium text-gray-600">or drop your file here</p> 194 - </div> 195 - </fieldset> 155 + onFiles={onFileDrop} 156 + /> 196 157 </div> 197 158 <hr class="mx-4 border-gray-300" /> 198 159
+5 -27
src/views/repository/repo-export.tsx
··· 1 1 import { type FileSystemFileHandle, showSaveFilePicker } from 'native-file-system-adapter'; 2 2 import { createSignal } from 'solid-js'; 3 3 4 - import { type AtprotoDid, getPdsEndpoint, isAtprotoDid, isHandle } from '@atcute/identity'; 4 + import { getPdsEndpoint, isAtprotoDid } from '@atcute/identity'; 5 + import { type AtprotoDid, isHandle } from '@atcute/lexicons/syntax'; 5 6 6 7 import { getDidDocument } from '~/api/queries/did-doc'; 7 8 import { resolveHandleViaAppView, resolveHandleViaPds } from '~/api/queries/handle'; 8 9 import { isServiceUrlString } from '~/api/types/strings'; 9 - import { DID_OR_HANDLE_RE } from '~/api/utils/strings'; 10 10 11 11 import { useTitle } from '~/lib/navigation/router'; 12 12 import { makeAbortable } from '~/lib/utils/abortable'; 13 13 import { formatBytes } from '~/lib/utils/intl/bytes'; 14 + import { iterateStream } from '~/lib/utils/stream'; 14 15 15 16 import Button from '~/components/inputs/button'; 16 17 import TextInput from '~/components/inputs/text-input'; 17 18 import Logger, { createLogger } from '~/components/logger'; 19 + import PageHeader from '~/components/page-header'; 18 20 19 21 const RepoExportPage = () => { 20 22 const logger = createLogger(); ··· 135 137 136 138 return ( 137 139 <> 138 - <div class="p-4"> 139 - <h1 class="text-lg font-bold text-purple-800">Export repository</h1> 140 - <p class="text-gray-600">Download an archive of an account's repository</p> 141 - </div> 142 - <hr class="mx-4 border-gray-300" /> 140 + <PageHeader title="Export repository" subtitle="Download an archive of an account's repository" /> 143 141 144 142 <form 145 143 onSubmit={(ev) => { ··· 190 188 type="text" 191 189 name="ident" 192 190 autocomplete="username" 193 - pattern={/* @once */ DID_OR_HANDLE_RE.source} 194 191 placeholder="paul.bsky.social" 195 192 autofocus 196 193 /> ··· 223 220 }; 224 221 225 222 export default RepoExportPage; 226 - 227 - export async function* iterateStream<T>(stream: ReadableStream<T>) { 228 - // Get a lock on the stream 229 - const reader = stream.getReader(); 230 - 231 - try { 232 - while (true) { 233 - const { done, value } = await reader.read(); 234 - 235 - if (done) { 236 - return; 237 - } 238 - 239 - yield value; 240 - } 241 - } finally { 242 - reader.releaseLock(); 243 - } 244 - }
+4 -2
tsconfig.app.json
··· 1 1 { 2 2 "compilerOptions": { 3 3 "target": "ESNext", 4 - "useDefineForClassFields": false, 5 4 "module": "ESNext", 6 5 "lib": ["ESNext", "DOM", "DOM.Iterable"], 7 - "types": [], 6 + "types": ["@atcute/atproto", "@atcute/bluesky"], 8 7 "skipLibCheck": true, 9 8 10 9 "moduleResolution": "Bundler", 11 10 "allowImportingTsExtensions": true, 12 11 "isolatedModules": true, 12 + "verbatimModuleSyntax": true, 13 13 "moduleDetection": "force", 14 14 "noEmit": true, 15 + 15 16 "jsx": "preserve", 16 17 "jsxImportSource": "solid-js", 18 + "useDefineForClassFields": false, 17 19 18 20 "strict": true, 19 21 "noUnusedLocals": true,
+9
wrangler.jsonc
··· 1 + { 2 + "$schema": "https://unpkg.com/wrangler@latest/config-schema.json", 3 + "name": "boat", 4 + "compatibility_date": "2025-10-05", 5 + "assets": { 6 + "directory": "dist", 7 + "not_found_handling": "single-page-application", 8 + }, 9 + }