+3
.gitignore
+3
.gitignore
-2
.npmrc
-2
.npmrc
+1
-1
.vscode/settings.json
+1
-1
.vscode/settings.json
+14
LICENSE
+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
-23
package.json
+32
-23
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.12",
11
-
"@atcute/car": "^2.0.1",
12
-
"@atcute/cbor": "^2.1.1",
13
-
"@atcute/client": "^2.0.7",
14
-
"@atcute/crypto": "^2.2.0",
15
-
"@atcute/multibase": "^1.1.2",
16
-
"@badrap/valita": "^0.4.2",
17
-
"@mary/array-fns": "npm:@jsr/mary__array-fns@^0.1.0",
18
-
"@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",
19
28
"@mary/solid-freeze": "npm:@externdefs/solid-freeze@^0.1.1",
20
-
"@mary/tar": "npm:@jsr/mary__tar@^0.2.4",
21
-
"nanoid": "^5.0.9",
29
+
"@mary/tar": "jsr:^0.3.1",
30
+
"nanoid": "^5.1.6",
22
31
"native-file-system-adapter": "^3.0.1",
23
-
"solid-js": "^1.9.4"
32
+
"solid-js": "^1.9.10"
24
33
},
25
34
"devDependencies": {
26
35
"@tailwindcss/forms": "^0.5.10",
27
-
"@types/node": "^22.12.0",
28
-
"autoprefixer": "^10.4.20",
29
-
"prettier": "^3.4.2",
30
-
"prettier-plugin-tailwindcss": "^0.6.11",
31
-
"tailwindcss": "^3.4.17",
32
-
"terser": "^5.37.0",
33
-
"typescript": "5.7.2",
34
-
"vite": "^6.0.11",
35
-
"vite-plugin-solid": "^2.11.0",
36
-
"wrangler": "^3.106.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"
37
46
}
38
47
}
+1378
-1224
pnpm-lock.yaml
+1378
-1224
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.12
13
-
version: 1.0.12(@atcute/client@2.0.7)
15
+
specifier: ^3.2.13
16
+
version: 3.2.13
14
17
'@atcute/car':
15
-
specifier: ^2.0.1
16
-
version: 2.0.1
18
+
specifier: ^5.0.0
19
+
version: 5.0.0
17
20
'@atcute/cbor':
18
-
specifier: ^2.1.1
19
-
version: 2.1.1
21
+
specifier: ^2.2.8
22
+
version: 2.2.8
23
+
'@atcute/cid':
24
+
specifier: ^2.2.6
25
+
version: 2.2.6
20
26
'@atcute/client':
21
-
specifier: ^2.0.7
22
-
version: 2.0.7
27
+
specifier: ^4.1.1
28
+
version: 4.1.1
23
29
'@atcute/crypto':
24
-
specifier: ^2.2.0
25
-
version: 2.2.0
30
+
specifier: ^2.3.0
31
+
version: 2.3.0
32
+
'@atcute/did-plc':
33
+
specifier: ^0.2.0
34
+
version: 0.2.0
35
+
'@atcute/identity':
36
+
specifier: ^1.1.3
37
+
version: 1.1.3
38
+
'@atcute/identity-resolver':
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
26
44
'@atcute/multibase':
27
-
specifier: ^1.1.2
28
-
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
50
+
'@atcute/tid':
51
+
specifier: ^1.0.3
52
+
version: 1.0.3
29
53
'@badrap/valita':
30
-
specifier: ^0.4.2
31
-
version: 0.4.2
54
+
specifier: ^0.4.6
55
+
version: 0.4.6
32
56
'@mary/array-fns':
33
-
specifier: npm:@jsr/mary__array-fns@^0.1.0
34
-
version: '@jsr/mary__array-fns@0.1.0'
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'
35
62
'@mary/events':
36
-
specifier: npm:@jsr/mary__events@^0.1.0
37
-
version: '@jsr/mary__events@0.1.0'
63
+
specifier: jsr:^0.2.0
64
+
version: '@jsr/mary__events@0.2.0'
38
65
'@mary/solid-freeze':
39
66
specifier: npm:@externdefs/solid-freeze@^0.1.1
40
-
version: '@externdefs/solid-freeze@0.1.1(solid-js@1.9.4)'
67
+
version: '@externdefs/solid-freeze@0.1.1(solid-js@1.9.10)'
41
68
'@mary/tar':
42
-
specifier: npm:@jsr/mary__tar@^0.2.4
43
-
version: '@jsr/mary__tar@0.2.4'
69
+
specifier: jsr:^0.3.1
70
+
version: '@jsr/mary__tar@0.3.1'
44
71
nanoid:
45
-
specifier: ^5.0.9
46
-
version: 5.0.9
72
+
specifier: ^5.1.6
73
+
version: 5.1.6
47
74
native-file-system-adapter:
48
75
specifier: ^3.0.1
49
76
version: 3.0.1
50
77
solid-js:
51
-
specifier: ^1.9.4
52
-
version: 1.9.4
78
+
specifier: ^1.9.10
79
+
version: 1.9.10
53
80
devDependencies:
54
81
'@tailwindcss/forms':
55
82
specifier: ^0.5.10
56
-
version: 0.5.10(tailwindcss@3.4.17)
83
+
version: 0.5.10(tailwindcss@3.4.18)
57
84
'@types/node':
58
-
specifier: ^22.12.0
59
-
version: 22.12.0
85
+
specifier: ^22.19.2
86
+
version: 22.19.2
60
87
autoprefixer:
61
-
specifier: ^10.4.20
62
-
version: 10.4.20(postcss@8.5.1)
88
+
specifier: ^10.4.22
89
+
version: 10.4.22(postcss@8.5.6)
63
90
prettier:
64
-
specifier: ^3.4.2
65
-
version: 3.4.2
91
+
specifier: ^3.7.4
92
+
version: 3.7.4
66
93
prettier-plugin-tailwindcss:
67
-
specifier: ^0.6.11
68
-
version: 0.6.11(prettier@3.4.2)
94
+
specifier: ^0.6.14
95
+
version: 0.6.14(prettier@3.7.4)
69
96
tailwindcss:
70
-
specifier: ^3.4.17
71
-
version: 3.4.17
97
+
specifier: ^3.4.18
98
+
version: 3.4.18
72
99
terser:
73
-
specifier: ^5.37.0
74
-
version: 5.37.0
100
+
specifier: ^5.44.1
101
+
version: 5.44.1
75
102
typescript:
76
-
specifier: 5.7.2
77
-
version: 5.7.2
103
+
specifier: ~5.9.3
104
+
version: 5.9.3
78
105
vite:
79
-
specifier: ^6.0.11
80
-
version: 6.0.11(@types/node@22.12.0)(jiti@1.21.7)(terser@5.37.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)
81
108
vite-plugin-solid:
82
-
specifier: ^2.11.0
83
-
version: 2.11.0(solid-js@1.9.4)(vite@6.0.11(@types/node@22.12.0)(jiti@1.21.7)(terser@5.37.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))
84
111
wrangler:
85
-
specifier: ^3.106.0
86
-
version: 3.106.0
112
+
specifier: ^4.53.0
113
+
version: 4.53.0
87
114
88
115
packages:
89
116
···
91
118
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
92
119
engines: {node: '>=10'}
93
120
94
-
'@ampproject/remapping@2.3.0':
95
-
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
96
-
engines: {node: '>=6.0.0'}
121
+
'@atcute/atproto@3.1.9':
122
+
resolution: {integrity: sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w==}
123
+
124
+
'@atcute/bluesky@3.2.13':
125
+
resolution: {integrity: sha512-ZG/mqsCjVU6zvH6XsRw+oQglrsdu5R7mnncMO+Ux0KWbX2xJw4ZMFHfs7ZTC69dVPK9r/yle7YbpygZTOWDM9A==}
97
126
98
-
'@atcute/bluesky@1.0.12':
99
-
resolution: {integrity: sha512-oUM+MxD5asGYyQDOHBGay7f9ryhsBpQ8LTUmsEZvp4t/WG0ZV2AcFRWsG0DxB+CsmSTbP2DHLMZCatE3usmt+g==}
127
+
'@atcute/car@5.0.0':
128
+
resolution: {integrity: sha512-OIY2xTXv8lSpZsDSn/UYQtJSMvDw5Hi4Q+uyvmiqSM+fht08QRAEq/nxa5YFciPZ3nfDFnZ3//EgJw7QhkSXLQ==}
129
+
130
+
'@atcute/cbor@2.2.8':
131
+
resolution: {integrity: sha512-UzOAN9BuN6JCXgn0ryV8qZuRJUDrNqrbLd6EFM8jc6RYssjRyGRxNy6RZ1NU/07Hd8Tq/0pz8+nQiMu5Zai5uw==}
132
+
133
+
'@atcute/cid@2.2.6':
134
+
resolution: {integrity: sha512-bTAHHbJ24p+E//V4KCS4xdmd39o211jJswvqQOevj7vk+5IYcgDLx1ryZWZ1sEPOo9x875li/kj5gpKL14RDwQ==}
135
+
136
+
'@atcute/client@4.1.1':
137
+
resolution: {integrity: sha512-FROCbTTCeL5u4tO/n72jDEKyKqjdlXMB56Ehve3W/gnnLGCYWvN42sS7tvL1Mgu6sbO3yZwsXKDrmM2No4XpjA==}
138
+
139
+
'@atcute/crypto@2.3.0':
140
+
resolution: {integrity: sha512-w5pkJKCjbNMQu+F4JRHbR3ROQyhi1wbn+GSC6WDQamcYHkZmEZk1/eoI354bIQOOfkEM6aFLv718iskrkon4GQ==}
141
+
142
+
'@atcute/did-plc@0.2.0':
143
+
resolution: {integrity: sha512-1sGek8GRM/Ph7nLVRREm8FqM7g4shGckItvdVwJcRbUa8Rh0zOsXQa0QyYWAC0k40BhkqO9FwKXhJEaXCmF5oQ==}
144
+
145
+
'@atcute/identity-resolver@1.2.0':
146
+
resolution: {integrity: sha512-5UbSJfdV3JIkF8ksXz7g4nKBWasf2wROvzM66cfvTIWydWFO6/oS1KZd+zo9Eokje5Scf5+jsY9ZfgVARLepXg==}
100
147
peerDependencies:
101
-
'@atcute/client': ^1.0.0 || ^2.0.0
148
+
'@atcute/identity': ^1.0.0
102
149
103
-
'@atcute/car@2.0.1':
104
-
resolution: {integrity: sha512-Z1YLniYwChJaHUWJH+99Ru6tNGI3xBngAdlM8qe3xij/wTUaIg14QgXLdWqk6smWTV+bR5IJ+Iiylt2zIJ+XXQ==}
150
+
'@atcute/identity@1.1.3':
151
+
resolution: {integrity: sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng==}
105
152
106
-
'@atcute/cbor@2.1.1':
107
-
resolution: {integrity: sha512-F4sHzsA5DNqMeCxvZKxbvDRIRKBTJom9IEQQutGMMFpvO0TZyIieEKgSL/7Us18TXPWKUXZM38KyCWIlPTLxeg==}
153
+
'@atcute/lexicons@1.2.5':
154
+
resolution: {integrity: sha512-9yO9WdgxW8jZ7SbzUycH710z+JmsQ9W9n5S6i6eghYju32kkluFmgBeS47r8e8p2+Dv4DemS7o/3SUGsX9FR5Q==}
108
155
109
-
'@atcute/cid@2.1.0':
110
-
resolution: {integrity: sha512-Twsf5OKGk6QEqU1Z74feo1eRmnznYFzdCxtCISCutedCL2I2eGzD1F7JZRA+heTp5kgV5Bwvxvjn7VkGQhq3Sg==}
156
+
'@atcute/mst@0.1.0':
157
+
resolution: {integrity: sha512-h+iDToKEnBpigk2DOHjSqY63vJtjYKUIztqu1CZ0P+I54wV2SrgoqAXAT1xrW6A1Iup8cjTv+U2H5WVG4KxPLw==}
111
158
112
-
'@atcute/client@2.0.7':
113
-
resolution: {integrity: sha512-bvNahrCGvhZw/EIx0HU/GOoKZEnUaAppbuZh7cu+VsOFA2tdFLnZJed9Hagh5Yz/eUX7QUh5NB4dRTRUdggSLQ==}
159
+
'@atcute/multibase@1.1.6':
160
+
resolution: {integrity: sha512-HBxuCgYLKPPxETV0Rot4VP9e24vKl8JdzGCZOVsDaOXJgbRZoRIF67Lp0H/OgnJeH/Xpva8Z5ReoTNJE5dn3kg==}
161
+
162
+
'@atcute/repo@0.1.0':
163
+
resolution: {integrity: sha512-INiYAuma8dydBu7cqd2WVpcXh3mzhIepYBUqFWAK5MqMulPRLTRCc/9GW3G9pxYrOdlvLCVamG2Jf8XK0nuFEw==}
114
164
115
-
'@atcute/crypto@2.2.0':
116
-
resolution: {integrity: sha512-Q/64Qn1AI8J0ZNy4hPDPpW/3poKf4OWRUxIYceCDI+btEOcIG5YMlhEQeZd6ojnI3oBMXy03sbOktekaBYRK9Q==}
165
+
'@atcute/tid@1.0.3':
166
+
resolution: {integrity: sha512-wfMJx1IMdnu0CZgWl0uR4JO2s6PGT1YPhpytD4ZHzEYKKQVuqV6Eb/7vieaVo1eYNMp2FrY67FZObeR7utRl2w==}
117
167
118
-
'@atcute/multibase@1.1.2':
119
-
resolution: {integrity: sha512-KFX+c7a/u2jSNcRw0rLaUHG/XEKf1A1c8XF5soHnsb1JMCShihf/anfZ1kJ4no/IlIp9HEHV3PQRQO2sWL6ASQ==}
168
+
'@atcute/uint8array@1.0.6':
169
+
resolution: {integrity: sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A==}
120
170
121
-
'@atcute/uint8array@1.0.1':
122
-
resolution: {integrity: sha512-AAnlFKyfDRgb9GNZJbhQ6OuMhbmNPirQyapb8KnmcEhxQZ3+tt+4NcwqekEegY4MpNqSTYeeTdyxq0wGZv1JHg==}
171
+
'@atcute/util-fetch@1.0.4':
172
+
resolution: {integrity: sha512-sIU9Qk0dE8PLEXSfhy+gIJV+HpiiknMytCI2SqLlqd0vgZUtEKI/EQfP+23LHWvP+CLCzVDOa6cpH045OlmNBg==}
123
173
124
-
'@atcute/varint@1.0.2':
125
-
resolution: {integrity: sha512-0O31hePzzr4O3NGWHUKKOyta6CGSL+AtN8iir8grGxu9jXyI7DBARlw6PbgKA6uTAvsXdpmRmF8MX+p0TsLnNg==}
174
+
'@atcute/varint@1.0.3':
175
+
resolution: {integrity: sha512-fdvMPyBB+McDT+Ai5e9RwEbwYV4yjZ60S2Dn5PTjGqUyxvoCH1z42viuheDZRUDkmfQehXJTZ5az7dSozVNtog==}
126
176
127
-
'@babel/code-frame@7.26.2':
128
-
resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
177
+
'@babel/code-frame@7.27.1':
178
+
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
129
179
engines: {node: '>=6.9.0'}
130
180
131
-
'@babel/compat-data@7.26.5':
132
-
resolution: {integrity: sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==}
181
+
'@babel/compat-data@7.28.5':
182
+
resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==}
133
183
engines: {node: '>=6.9.0'}
134
184
135
-
'@babel/core@7.26.7':
136
-
resolution: {integrity: sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==}
185
+
'@babel/core@7.28.5':
186
+
resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==}
137
187
engines: {node: '>=6.9.0'}
138
188
139
-
'@babel/generator@7.26.5':
140
-
resolution: {integrity: sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==}
189
+
'@babel/generator@7.28.5':
190
+
resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
141
191
engines: {node: '>=6.9.0'}
142
192
143
-
'@babel/helper-compilation-targets@7.26.5':
144
-
resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==}
193
+
'@babel/helper-compilation-targets@7.27.2':
194
+
resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
195
+
engines: {node: '>=6.9.0'}
196
+
197
+
'@babel/helper-globals@7.28.0':
198
+
resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
145
199
engines: {node: '>=6.9.0'}
146
200
147
201
'@babel/helper-module-imports@7.18.6':
148
202
resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
149
203
engines: {node: '>=6.9.0'}
150
204
151
-
'@babel/helper-module-imports@7.25.9':
152
-
resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
205
+
'@babel/helper-module-imports@7.27.1':
206
+
resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
153
207
engines: {node: '>=6.9.0'}
154
208
155
-
'@babel/helper-module-transforms@7.26.0':
156
-
resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
209
+
'@babel/helper-module-transforms@7.28.3':
210
+
resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==}
157
211
engines: {node: '>=6.9.0'}
158
212
peerDependencies:
159
213
'@babel/core': ^7.0.0
160
214
161
-
'@babel/helper-plugin-utils@7.26.5':
162
-
resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==}
215
+
'@babel/helper-plugin-utils@7.27.1':
216
+
resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
163
217
engines: {node: '>=6.9.0'}
164
218
165
-
'@babel/helper-string-parser@7.25.9':
166
-
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
219
+
'@babel/helper-string-parser@7.27.1':
220
+
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
167
221
engines: {node: '>=6.9.0'}
168
222
169
-
'@babel/helper-validator-identifier@7.25.9':
170
-
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
223
+
'@babel/helper-validator-identifier@7.28.5':
224
+
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
171
225
engines: {node: '>=6.9.0'}
172
226
173
-
'@babel/helper-validator-option@7.25.9':
174
-
resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
227
+
'@babel/helper-validator-option@7.27.1':
228
+
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
175
229
engines: {node: '>=6.9.0'}
176
230
177
-
'@babel/helpers@7.26.7':
178
-
resolution: {integrity: sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==}
231
+
'@babel/helpers@7.28.4':
232
+
resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
179
233
engines: {node: '>=6.9.0'}
180
234
181
-
'@babel/parser@7.26.7':
182
-
resolution: {integrity: sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==}
235
+
'@babel/parser@7.28.5':
236
+
resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
183
237
engines: {node: '>=6.0.0'}
184
238
hasBin: true
185
239
186
-
'@babel/plugin-syntax-jsx@7.25.9':
187
-
resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==}
240
+
'@babel/plugin-syntax-jsx@7.27.1':
241
+
resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==}
188
242
engines: {node: '>=6.9.0'}
189
243
peerDependencies:
190
244
'@babel/core': ^7.0.0-0
191
245
192
-
'@babel/template@7.25.9':
193
-
resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==}
246
+
'@babel/template@7.27.2':
247
+
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
194
248
engines: {node: '>=6.9.0'}
195
249
196
-
'@babel/traverse@7.26.7':
197
-
resolution: {integrity: sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==}
250
+
'@babel/traverse@7.28.5':
251
+
resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==}
198
252
engines: {node: '>=6.9.0'}
199
253
200
-
'@babel/types@7.26.7':
201
-
resolution: {integrity: sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==}
254
+
'@babel/types@7.28.5':
255
+
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
202
256
engines: {node: '>=6.9.0'}
203
257
204
-
'@badrap/valita@0.4.2':
205
-
resolution: {integrity: sha512-Mwmr7k2iK0Yy0POLnAFUgab2mxKYeIsYXHY7sg3jo8XFsFHbG0SBmTcktXD0uW8N4WZePKf8s68QV7QDTGSdHA==}
258
+
'@badrap/valita@0.4.6':
259
+
resolution: {integrity: sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==}
206
260
engines: {node: '>= 18'}
207
261
208
-
'@cloudflare/kv-asset-handler@0.3.4':
209
-
resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==}
210
-
engines: {node: '>=16.13'}
262
+
'@cloudflare/kv-asset-handler@0.4.1':
263
+
resolution: {integrity: sha512-Nu8ahitGFFJztxUml9oD/DLb7Z28C8cd8F46IVQ7y5Btz575pvMY8AqZsXkX7Gds29eCKdMgIHjIvzskHgPSFg==}
264
+
engines: {node: '>=18.0.0'}
265
+
266
+
'@cloudflare/unenv-preset@2.7.13':
267
+
resolution: {integrity: sha512-NulO1H8R/DzsJguLC0ndMuk4Ufv0KSlN+E54ay9rn9ZCQo0kpAPwwh3LhgpZ96a3Dr6L9LqW57M4CqC34iLOvw==}
268
+
peerDependencies:
269
+
unenv: 2.0.0-rc.24
270
+
workerd: ^1.20251202.0
271
+
peerDependenciesMeta:
272
+
workerd:
273
+
optional: true
211
274
212
-
'@cloudflare/workerd-darwin-64@1.20250124.0':
213
-
resolution: {integrity: sha512-P5Z5KfVAuoCidIc0o2JPQZFLNTXDjtxN8vhtreCUr6V+xF5pqDNwQqeBDnDDF0gcszFQOYi2OZAB9e1MwssTwA==}
275
+
'@cloudflare/workerd-darwin-64@1.20251202.0':
276
+
resolution: {integrity: sha512-/uvEAWEukTWb1geHhbjGUeZqcSSSyYzp0mvoPUBl+l0ont4NVGao3fgwM0q8wtKvgoKCHSG6zcG23wj9Opj3Nw==}
214
277
engines: {node: '>=16'}
215
278
cpu: [x64]
216
279
os: [darwin]
217
280
218
-
'@cloudflare/workerd-darwin-arm64@1.20250124.0':
219
-
resolution: {integrity: sha512-lVxf6qIfmJ5rS6rmGKV7lt6ApY6nhD4kAQTK4vKYm/npk2sXod6LASIY0U4WBCwy4N+S75a8hP2QtmQf+KV3Iw==}
281
+
'@cloudflare/workerd-darwin-arm64@1.20251202.0':
282
+
resolution: {integrity: sha512-f52xRvcI9cWRd6400EZStRtXiRC5XKEud7K5aFIbbUv0VeINltujFQQ9nHWtsF6g1quIXWkjhh5u01gPAYNNXA==}
220
283
engines: {node: '>=16'}
221
284
cpu: [arm64]
222
285
os: [darwin]
223
286
224
-
'@cloudflare/workerd-linux-64@1.20250124.0':
225
-
resolution: {integrity: sha512-5S4GzN08vW/CfzaM1rVAkRhPPSDX1O1t7u0pj+xdbGl4GcazBzE4ZLre+y9OMplZ9PBCkxXkRWqHXzabWA1x4A==}
287
+
'@cloudflare/workerd-linux-64@1.20251202.0':
288
+
resolution: {integrity: sha512-HYXinF5RBH7oXbsFUMmwKCj+WltpYbf5mRKUBG5v3EuPhUjSIFB84U+58pDyfBJjcynHdy3EtvTWcvh/+lcgow==}
226
289
engines: {node: '>=16'}
227
290
cpu: [x64]
228
291
os: [linux]
229
292
230
-
'@cloudflare/workerd-linux-arm64@1.20250124.0':
231
-
resolution: {integrity: sha512-CHSYnutDfXgUWL9WcP0GbzIb5OyC9RZVCJGhKbDTQy6/uH7AivNcLzXtOhNdqetKjERmOxUbL9Us7vcMQLztog==}
293
+
'@cloudflare/workerd-linux-arm64@1.20251202.0':
294
+
resolution: {integrity: sha512-++L02Jdoxz7hEA9qDaQjbVU1RzQS+S+eqIi22DkPe2Tgiq2M3UfNpeu+75k5L9DGRIkZPYvwMBMbcmKvQqdIIg==}
232
295
engines: {node: '>=16'}
233
296
cpu: [arm64]
234
297
os: [linux]
235
298
236
-
'@cloudflare/workerd-windows-64@1.20250124.0':
237
-
resolution: {integrity: sha512-5TunEy5x4pNUQ10Z47qP5iF6m3X9uB2ZScKDLkNaWtbQ7EcMCapOWzuynVkTKIMBgDeKw6DAB8nbbkybPyMS9w==}
299
+
'@cloudflare/workerd-windows-64@1.20251202.0':
300
+
resolution: {integrity: sha512-gzeU6eDydTi7ib+Q9DD/c0hpXtqPucnHk2tfGU03mljPObYxzMkkPGgB5qxpksFvub3y4K0ChjqYxGJB4F+j3g==}
238
301
engines: {node: '>=16'}
239
302
cpu: [x64]
240
303
os: [win32]
···
243
306
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
244
307
engines: {node: '>=12'}
245
308
246
-
'@esbuild-plugins/node-globals-polyfill@0.2.3':
247
-
resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==}
248
-
peerDependencies:
249
-
esbuild: '*'
309
+
'@emnapi/runtime@1.7.1':
310
+
resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}
250
311
251
-
'@esbuild-plugins/node-modules-polyfill@0.2.2':
252
-
resolution: {integrity: sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==}
253
-
peerDependencies:
254
-
esbuild: '*'
312
+
'@esbuild/aix-ppc64@0.25.12':
313
+
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
314
+
engines: {node: '>=18'}
315
+
cpu: [ppc64]
316
+
os: [aix]
255
317
256
-
'@esbuild/aix-ppc64@0.24.2':
257
-
resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==}
318
+
'@esbuild/aix-ppc64@0.27.0':
319
+
resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==}
258
320
engines: {node: '>=18'}
259
321
cpu: [ppc64]
260
322
os: [aix]
261
323
262
-
'@esbuild/android-arm64@0.17.19':
263
-
resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==}
264
-
engines: {node: '>=12'}
324
+
'@esbuild/android-arm64@0.25.12':
325
+
resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
326
+
engines: {node: '>=18'}
265
327
cpu: [arm64]
266
328
os: [android]
267
329
268
-
'@esbuild/android-arm64@0.24.2':
269
-
resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==}
330
+
'@esbuild/android-arm64@0.27.0':
331
+
resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==}
270
332
engines: {node: '>=18'}
271
333
cpu: [arm64]
272
334
os: [android]
273
335
274
-
'@esbuild/android-arm@0.17.19':
275
-
resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==}
276
-
engines: {node: '>=12'}
336
+
'@esbuild/android-arm@0.25.12':
337
+
resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
338
+
engines: {node: '>=18'}
277
339
cpu: [arm]
278
340
os: [android]
279
341
280
-
'@esbuild/android-arm@0.24.2':
281
-
resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==}
342
+
'@esbuild/android-arm@0.27.0':
343
+
resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==}
282
344
engines: {node: '>=18'}
283
345
cpu: [arm]
284
346
os: [android]
285
347
286
-
'@esbuild/android-x64@0.17.19':
287
-
resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==}
288
-
engines: {node: '>=12'}
348
+
'@esbuild/android-x64@0.25.12':
349
+
resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
350
+
engines: {node: '>=18'}
289
351
cpu: [x64]
290
352
os: [android]
291
353
292
-
'@esbuild/android-x64@0.24.2':
293
-
resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==}
354
+
'@esbuild/android-x64@0.27.0':
355
+
resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==}
294
356
engines: {node: '>=18'}
295
357
cpu: [x64]
296
358
os: [android]
297
359
298
-
'@esbuild/darwin-arm64@0.17.19':
299
-
resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==}
300
-
engines: {node: '>=12'}
360
+
'@esbuild/darwin-arm64@0.25.12':
361
+
resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
362
+
engines: {node: '>=18'}
301
363
cpu: [arm64]
302
364
os: [darwin]
303
365
304
-
'@esbuild/darwin-arm64@0.24.2':
305
-
resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==}
366
+
'@esbuild/darwin-arm64@0.27.0':
367
+
resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==}
306
368
engines: {node: '>=18'}
307
369
cpu: [arm64]
308
370
os: [darwin]
309
371
310
-
'@esbuild/darwin-x64@0.17.19':
311
-
resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==}
312
-
engines: {node: '>=12'}
372
+
'@esbuild/darwin-x64@0.25.12':
373
+
resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
374
+
engines: {node: '>=18'}
313
375
cpu: [x64]
314
376
os: [darwin]
315
377
316
-
'@esbuild/darwin-x64@0.24.2':
317
-
resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==}
378
+
'@esbuild/darwin-x64@0.27.0':
379
+
resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==}
318
380
engines: {node: '>=18'}
319
381
cpu: [x64]
320
382
os: [darwin]
321
383
322
-
'@esbuild/freebsd-arm64@0.17.19':
323
-
resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==}
324
-
engines: {node: '>=12'}
384
+
'@esbuild/freebsd-arm64@0.25.12':
385
+
resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
386
+
engines: {node: '>=18'}
325
387
cpu: [arm64]
326
388
os: [freebsd]
327
389
328
-
'@esbuild/freebsd-arm64@0.24.2':
329
-
resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==}
390
+
'@esbuild/freebsd-arm64@0.27.0':
391
+
resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==}
330
392
engines: {node: '>=18'}
331
393
cpu: [arm64]
332
394
os: [freebsd]
333
395
334
-
'@esbuild/freebsd-x64@0.17.19':
335
-
resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==}
336
-
engines: {node: '>=12'}
396
+
'@esbuild/freebsd-x64@0.25.12':
397
+
resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
398
+
engines: {node: '>=18'}
337
399
cpu: [x64]
338
400
os: [freebsd]
339
401
340
-
'@esbuild/freebsd-x64@0.24.2':
341
-
resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==}
402
+
'@esbuild/freebsd-x64@0.27.0':
403
+
resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==}
342
404
engines: {node: '>=18'}
343
405
cpu: [x64]
344
406
os: [freebsd]
345
407
346
-
'@esbuild/linux-arm64@0.17.19':
347
-
resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==}
348
-
engines: {node: '>=12'}
408
+
'@esbuild/linux-arm64@0.25.12':
409
+
resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
410
+
engines: {node: '>=18'}
349
411
cpu: [arm64]
350
412
os: [linux]
351
413
352
-
'@esbuild/linux-arm64@0.24.2':
353
-
resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==}
414
+
'@esbuild/linux-arm64@0.27.0':
415
+
resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==}
354
416
engines: {node: '>=18'}
355
417
cpu: [arm64]
356
418
os: [linux]
357
419
358
-
'@esbuild/linux-arm@0.17.19':
359
-
resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==}
360
-
engines: {node: '>=12'}
420
+
'@esbuild/linux-arm@0.25.12':
421
+
resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
422
+
engines: {node: '>=18'}
361
423
cpu: [arm]
362
424
os: [linux]
363
425
364
-
'@esbuild/linux-arm@0.24.2':
365
-
resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==}
426
+
'@esbuild/linux-arm@0.27.0':
427
+
resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==}
366
428
engines: {node: '>=18'}
367
429
cpu: [arm]
368
430
os: [linux]
369
431
370
-
'@esbuild/linux-ia32@0.17.19':
371
-
resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==}
372
-
engines: {node: '>=12'}
432
+
'@esbuild/linux-ia32@0.25.12':
433
+
resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
434
+
engines: {node: '>=18'}
373
435
cpu: [ia32]
374
436
os: [linux]
375
437
376
-
'@esbuild/linux-ia32@0.24.2':
377
-
resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==}
438
+
'@esbuild/linux-ia32@0.27.0':
439
+
resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==}
378
440
engines: {node: '>=18'}
379
441
cpu: [ia32]
380
442
os: [linux]
381
443
382
-
'@esbuild/linux-loong64@0.17.19':
383
-
resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==}
384
-
engines: {node: '>=12'}
444
+
'@esbuild/linux-loong64@0.25.12':
445
+
resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
446
+
engines: {node: '>=18'}
385
447
cpu: [loong64]
386
448
os: [linux]
387
449
388
-
'@esbuild/linux-loong64@0.24.2':
389
-
resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==}
450
+
'@esbuild/linux-loong64@0.27.0':
451
+
resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==}
390
452
engines: {node: '>=18'}
391
453
cpu: [loong64]
392
454
os: [linux]
393
455
394
-
'@esbuild/linux-mips64el@0.17.19':
395
-
resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==}
396
-
engines: {node: '>=12'}
456
+
'@esbuild/linux-mips64el@0.25.12':
457
+
resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
458
+
engines: {node: '>=18'}
397
459
cpu: [mips64el]
398
460
os: [linux]
399
461
400
-
'@esbuild/linux-mips64el@0.24.2':
401
-
resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==}
462
+
'@esbuild/linux-mips64el@0.27.0':
463
+
resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==}
402
464
engines: {node: '>=18'}
403
465
cpu: [mips64el]
404
466
os: [linux]
405
467
406
-
'@esbuild/linux-ppc64@0.17.19':
407
-
resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==}
408
-
engines: {node: '>=12'}
468
+
'@esbuild/linux-ppc64@0.25.12':
469
+
resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
470
+
engines: {node: '>=18'}
409
471
cpu: [ppc64]
410
472
os: [linux]
411
473
412
-
'@esbuild/linux-ppc64@0.24.2':
413
-
resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==}
474
+
'@esbuild/linux-ppc64@0.27.0':
475
+
resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==}
414
476
engines: {node: '>=18'}
415
477
cpu: [ppc64]
416
478
os: [linux]
417
479
418
-
'@esbuild/linux-riscv64@0.17.19':
419
-
resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==}
420
-
engines: {node: '>=12'}
480
+
'@esbuild/linux-riscv64@0.25.12':
481
+
resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
482
+
engines: {node: '>=18'}
421
483
cpu: [riscv64]
422
484
os: [linux]
423
485
424
-
'@esbuild/linux-riscv64@0.24.2':
425
-
resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==}
486
+
'@esbuild/linux-riscv64@0.27.0':
487
+
resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==}
426
488
engines: {node: '>=18'}
427
489
cpu: [riscv64]
428
490
os: [linux]
429
491
430
-
'@esbuild/linux-s390x@0.17.19':
431
-
resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==}
432
-
engines: {node: '>=12'}
492
+
'@esbuild/linux-s390x@0.25.12':
493
+
resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
494
+
engines: {node: '>=18'}
433
495
cpu: [s390x]
434
496
os: [linux]
435
497
436
-
'@esbuild/linux-s390x@0.24.2':
437
-
resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==}
498
+
'@esbuild/linux-s390x@0.27.0':
499
+
resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==}
438
500
engines: {node: '>=18'}
439
501
cpu: [s390x]
440
502
os: [linux]
441
503
442
-
'@esbuild/linux-x64@0.17.19':
443
-
resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==}
444
-
engines: {node: '>=12'}
504
+
'@esbuild/linux-x64@0.25.12':
505
+
resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
506
+
engines: {node: '>=18'}
445
507
cpu: [x64]
446
508
os: [linux]
447
509
448
-
'@esbuild/linux-x64@0.24.2':
449
-
resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==}
510
+
'@esbuild/linux-x64@0.27.0':
511
+
resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==}
450
512
engines: {node: '>=18'}
451
513
cpu: [x64]
452
514
os: [linux]
453
515
454
-
'@esbuild/netbsd-arm64@0.24.2':
455
-
resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==}
516
+
'@esbuild/netbsd-arm64@0.25.12':
517
+
resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
456
518
engines: {node: '>=18'}
457
519
cpu: [arm64]
458
520
os: [netbsd]
459
521
460
-
'@esbuild/netbsd-x64@0.17.19':
461
-
resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==}
462
-
engines: {node: '>=12'}
522
+
'@esbuild/netbsd-arm64@0.27.0':
523
+
resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==}
524
+
engines: {node: '>=18'}
525
+
cpu: [arm64]
526
+
os: [netbsd]
527
+
528
+
'@esbuild/netbsd-x64@0.25.12':
529
+
resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
530
+
engines: {node: '>=18'}
463
531
cpu: [x64]
464
532
os: [netbsd]
465
533
466
-
'@esbuild/netbsd-x64@0.24.2':
467
-
resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==}
534
+
'@esbuild/netbsd-x64@0.27.0':
535
+
resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==}
468
536
engines: {node: '>=18'}
469
537
cpu: [x64]
470
538
os: [netbsd]
471
539
472
-
'@esbuild/openbsd-arm64@0.24.2':
473
-
resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==}
540
+
'@esbuild/openbsd-arm64@0.25.12':
541
+
resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
542
+
engines: {node: '>=18'}
543
+
cpu: [arm64]
544
+
os: [openbsd]
545
+
546
+
'@esbuild/openbsd-arm64@0.27.0':
547
+
resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==}
474
548
engines: {node: '>=18'}
475
549
cpu: [arm64]
476
550
os: [openbsd]
477
551
478
-
'@esbuild/openbsd-x64@0.17.19':
479
-
resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==}
480
-
engines: {node: '>=12'}
552
+
'@esbuild/openbsd-x64@0.25.12':
553
+
resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
554
+
engines: {node: '>=18'}
481
555
cpu: [x64]
482
556
os: [openbsd]
483
557
484
-
'@esbuild/openbsd-x64@0.24.2':
485
-
resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==}
558
+
'@esbuild/openbsd-x64@0.27.0':
559
+
resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==}
486
560
engines: {node: '>=18'}
487
561
cpu: [x64]
488
562
os: [openbsd]
489
563
490
-
'@esbuild/sunos-x64@0.17.19':
491
-
resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==}
492
-
engines: {node: '>=12'}
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==}
578
+
engines: {node: '>=18'}
493
579
cpu: [x64]
494
580
os: [sunos]
495
581
496
-
'@esbuild/sunos-x64@0.24.2':
497
-
resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==}
582
+
'@esbuild/sunos-x64@0.27.0':
583
+
resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==}
498
584
engines: {node: '>=18'}
499
585
cpu: [x64]
500
586
os: [sunos]
501
587
502
-
'@esbuild/win32-arm64@0.17.19':
503
-
resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==}
504
-
engines: {node: '>=12'}
588
+
'@esbuild/win32-arm64@0.25.12':
589
+
resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
590
+
engines: {node: '>=18'}
505
591
cpu: [arm64]
506
592
os: [win32]
507
593
508
-
'@esbuild/win32-arm64@0.24.2':
509
-
resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==}
594
+
'@esbuild/win32-arm64@0.27.0':
595
+
resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==}
510
596
engines: {node: '>=18'}
511
597
cpu: [arm64]
512
598
os: [win32]
513
599
514
-
'@esbuild/win32-ia32@0.17.19':
515
-
resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==}
516
-
engines: {node: '>=12'}
600
+
'@esbuild/win32-ia32@0.25.12':
601
+
resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
602
+
engines: {node: '>=18'}
517
603
cpu: [ia32]
518
604
os: [win32]
519
605
520
-
'@esbuild/win32-ia32@0.24.2':
521
-
resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==}
606
+
'@esbuild/win32-ia32@0.27.0':
607
+
resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==}
522
608
engines: {node: '>=18'}
523
609
cpu: [ia32]
524
610
os: [win32]
525
611
526
-
'@esbuild/win32-x64@0.17.19':
527
-
resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==}
528
-
engines: {node: '>=12'}
612
+
'@esbuild/win32-x64@0.25.12':
613
+
resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
614
+
engines: {node: '>=18'}
529
615
cpu: [x64]
530
616
os: [win32]
531
617
532
-
'@esbuild/win32-x64@0.24.2':
533
-
resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==}
618
+
'@esbuild/win32-x64@0.27.0':
619
+
resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==}
534
620
engines: {node: '>=18'}
535
621
cpu: [x64]
536
622
os: [win32]
···
540
626
peerDependencies:
541
627
solid-js: ^1.8.5
542
628
543
-
'@fastify/busboy@2.1.1':
544
-
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
545
-
engines: {node: '>=14'}
629
+
'@img/sharp-darwin-arm64@0.33.5':
630
+
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
631
+
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
632
+
cpu: [arm64]
633
+
os: [darwin]
634
+
635
+
'@img/sharp-darwin-x64@0.33.5':
636
+
resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==}
637
+
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
638
+
cpu: [x64]
639
+
os: [darwin]
640
+
641
+
'@img/sharp-libvips-darwin-arm64@1.0.4':
642
+
resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
643
+
cpu: [arm64]
644
+
os: [darwin]
645
+
646
+
'@img/sharp-libvips-darwin-x64@1.0.4':
647
+
resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==}
648
+
cpu: [x64]
649
+
os: [darwin]
650
+
651
+
'@img/sharp-libvips-linux-arm64@1.0.4':
652
+
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
653
+
cpu: [arm64]
654
+
os: [linux]
655
+
656
+
'@img/sharp-libvips-linux-arm@1.0.5':
657
+
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
658
+
cpu: [arm]
659
+
os: [linux]
660
+
661
+
'@img/sharp-libvips-linux-s390x@1.0.4':
662
+
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
663
+
cpu: [s390x]
664
+
os: [linux]
665
+
666
+
'@img/sharp-libvips-linux-x64@1.0.4':
667
+
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
668
+
cpu: [x64]
669
+
os: [linux]
670
+
671
+
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
672
+
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
673
+
cpu: [arm64]
674
+
os: [linux]
675
+
676
+
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
677
+
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
678
+
cpu: [x64]
679
+
os: [linux]
680
+
681
+
'@img/sharp-linux-arm64@0.33.5':
682
+
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
683
+
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
684
+
cpu: [arm64]
685
+
os: [linux]
686
+
687
+
'@img/sharp-linux-arm@0.33.5':
688
+
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
689
+
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
690
+
cpu: [arm]
691
+
os: [linux]
692
+
693
+
'@img/sharp-linux-s390x@0.33.5':
694
+
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
695
+
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
696
+
cpu: [s390x]
697
+
os: [linux]
698
+
699
+
'@img/sharp-linux-x64@0.33.5':
700
+
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
701
+
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
702
+
cpu: [x64]
703
+
os: [linux]
704
+
705
+
'@img/sharp-linuxmusl-arm64@0.33.5':
706
+
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
707
+
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
708
+
cpu: [arm64]
709
+
os: [linux]
710
+
711
+
'@img/sharp-linuxmusl-x64@0.33.5':
712
+
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
713
+
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
714
+
cpu: [x64]
715
+
os: [linux]
716
+
717
+
'@img/sharp-wasm32@0.33.5':
718
+
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
719
+
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
720
+
cpu: [wasm32]
721
+
722
+
'@img/sharp-win32-ia32@0.33.5':
723
+
resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==}
724
+
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
725
+
cpu: [ia32]
726
+
os: [win32]
727
+
728
+
'@img/sharp-win32-x64@0.33.5':
729
+
resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
730
+
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
731
+
cpu: [x64]
732
+
os: [win32]
546
733
547
-
'@isaacs/cliui@8.0.2':
548
-
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
549
-
engines: {node: '>=12'}
734
+
'@jridgewell/gen-mapping@0.3.13':
735
+
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
550
736
551
-
'@jridgewell/gen-mapping@0.3.8':
552
-
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
553
-
engines: {node: '>=6.0.0'}
737
+
'@jridgewell/remapping@2.3.5':
738
+
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
554
739
555
740
'@jridgewell/resolve-uri@3.1.2':
556
741
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
557
742
engines: {node: '>=6.0.0'}
558
743
559
-
'@jridgewell/set-array@1.2.1':
560
-
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
561
-
engines: {node: '>=6.0.0'}
562
-
563
-
'@jridgewell/source-map@0.3.6':
564
-
resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==}
744
+
'@jridgewell/source-map@0.3.11':
745
+
resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==}
565
746
566
-
'@jridgewell/sourcemap-codec@1.5.0':
567
-
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
747
+
'@jridgewell/sourcemap-codec@1.5.5':
748
+
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
568
749
569
-
'@jridgewell/trace-mapping@0.3.25':
570
-
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
750
+
'@jridgewell/trace-mapping@0.3.31':
751
+
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
571
752
572
753
'@jridgewell/trace-mapping@0.3.9':
573
754
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
574
755
575
-
'@jsr/mary__array-fns@0.1.0':
576
-
resolution: {integrity: sha512-rG8Ng1Arl86T1Lv4of4LC8OcdpGxcxG/j8K01K6lyG0lI9imLT7e6FknuMVs5MQ5jH6NUBYnPOfmVAv4sd/OkA==, tarball: https://npm.jsr.io/~/11/@jsr/mary__array-fns/0.1.0.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}
577
758
578
-
'@jsr/mary__events@0.1.0':
579
-
resolution: {integrity: sha512-oS6jVOaXTaNEa6avRncwrEtUYaBKrq/HEybPa9Z3aoeMs+RSly0vn0KcOj/fy2H6iTBkeh3wa8+/9nFjhKyKIg==, tarball: https://npm.jsr.io/~/11/@jsr/mary__events/0.1.0.tgz}
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}
580
761
581
-
'@jsr/mary__tar@0.2.4':
582
-
resolution: {integrity: sha512-jFjPcZj8DRSukPLZOt6+h74cVFdfdTMG9gzbW67YByCJTD52PEpe2sNcfCSw4mQ8hZBNgwiufCPyYL8hR9yicA==, tarball: https://npm.jsr.io/~/11/@jsr/mary__tar/0.2.4.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}
764
+
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}
583
767
584
-
'@noble/secp256k1@2.2.3':
585
-
resolution: {integrity: sha512-l7r5oEQym9Us7EAigzg30/PQAvynhMt2uoYtT3t26eGDVm9Yii5mZ5jWSWmZ/oSIR2Et0xfc6DXrG0bZ787V3w==}
768
+
'@noble/secp256k1@3.0.0':
769
+
resolution: {integrity: sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==}
586
770
587
771
'@nodelib/fs.scandir@2.1.5':
588
772
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
···
596
780
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
597
781
engines: {node: '>= 8'}
598
782
599
-
'@pkgjs/parseargs@0.11.0':
600
-
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
601
-
engines: {node: '>=14'}
783
+
'@poppinss/colors@4.1.5':
784
+
resolution: {integrity: sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==}
602
785
603
-
'@rollup/rollup-android-arm-eabi@4.32.1':
604
-
resolution: {integrity: sha512-/pqA4DmqyCm8u5YIDzIdlLcEmuvxb0v8fZdFhVMszSpDTgbQKdw3/mB3eMUHIbubtJ6F9j+LtmyCnHTEqIHyzA==}
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==}
791
+
792
+
'@rollup/rollup-android-arm-eabi@4.53.3':
793
+
resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==}
605
794
cpu: [arm]
606
795
os: [android]
607
796
608
-
'@rollup/rollup-android-arm64@4.32.1':
609
-
resolution: {integrity: sha512-If3PDskT77q7zgqVqYuj7WG3WC08G1kwXGVFi9Jr8nY6eHucREHkfpX79c0ACAjLj3QIWKPJR7w4i+f5EdLH5Q==}
797
+
'@rollup/rollup-android-arm64@4.53.3':
798
+
resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==}
610
799
cpu: [arm64]
611
800
os: [android]
612
801
613
-
'@rollup/rollup-darwin-arm64@4.32.1':
614
-
resolution: {integrity: sha512-zCpKHioQ9KgZToFp5Wvz6zaWbMzYQ2LJHQ+QixDKq52KKrF65ueu6Af4hLlLWHjX1Wf/0G5kSJM9PySW9IrvHA==}
802
+
'@rollup/rollup-darwin-arm64@4.53.3':
803
+
resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==}
615
804
cpu: [arm64]
616
805
os: [darwin]
617
806
618
-
'@rollup/rollup-darwin-x64@4.32.1':
619
-
resolution: {integrity: sha512-sFvF+t2+TyUo/ZQqUcifrJIgznx58oFZbdHS9TvHq3xhPVL9nOp+yZ6LKrO9GWTP+6DbFtoyLDbjTpR62Mbr3Q==}
807
+
'@rollup/rollup-darwin-x64@4.53.3':
808
+
resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==}
620
809
cpu: [x64]
621
810
os: [darwin]
622
811
623
-
'@rollup/rollup-freebsd-arm64@4.32.1':
624
-
resolution: {integrity: sha512-NbOa+7InvMWRcY9RG+B6kKIMD/FsnQPH0MWUvDlQB1iXnF/UcKSudCXZtv4lW+C276g3w5AxPbfry5rSYvyeYA==}
812
+
'@rollup/rollup-freebsd-arm64@4.53.3':
813
+
resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==}
625
814
cpu: [arm64]
626
815
os: [freebsd]
627
816
628
-
'@rollup/rollup-freebsd-x64@4.32.1':
629
-
resolution: {integrity: sha512-JRBRmwvHPXR881j2xjry8HZ86wIPK2CcDw0EXchE1UgU0ubWp9nvlT7cZYKc6bkypBt745b4bglf3+xJ7hXWWw==}
817
+
'@rollup/rollup-freebsd-x64@4.53.3':
818
+
resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==}
630
819
cpu: [x64]
631
820
os: [freebsd]
632
821
633
-
'@rollup/rollup-linux-arm-gnueabihf@4.32.1':
634
-
resolution: {integrity: sha512-PKvszb+9o/vVdUzCCjL0sKHukEQV39tD3fepXxYrHE3sTKrRdCydI7uldRLbjLmDA3TFDmh418XH19NOsDRH8g==}
822
+
'@rollup/rollup-linux-arm-gnueabihf@4.53.3':
823
+
resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==}
635
824
cpu: [arm]
636
825
os: [linux]
637
826
638
-
'@rollup/rollup-linux-arm-musleabihf@4.32.1':
639
-
resolution: {integrity: sha512-9WHEMV6Y89eL606ReYowXuGF1Yb2vwfKWKdD1A5h+OYnPZSJvxbEjxTRKPgi7tkP2DSnW0YLab1ooy+i/FQp/Q==}
827
+
'@rollup/rollup-linux-arm-musleabihf@4.53.3':
828
+
resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==}
640
829
cpu: [arm]
641
830
os: [linux]
642
831
643
-
'@rollup/rollup-linux-arm64-gnu@4.32.1':
644
-
resolution: {integrity: sha512-tZWc9iEt5fGJ1CL2LRPw8OttkCBDs+D8D3oEM8mH8S1ICZCtFJhD7DZ3XMGM8kpqHvhGUTvNUYVDnmkj4BDXnw==}
832
+
'@rollup/rollup-linux-arm64-gnu@4.53.3':
833
+
resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==}
645
834
cpu: [arm64]
646
835
os: [linux]
647
836
648
-
'@rollup/rollup-linux-arm64-musl@4.32.1':
649
-
resolution: {integrity: sha512-FTYc2YoTWUsBz5GTTgGkRYYJ5NGJIi/rCY4oK/I8aKowx1ToXeoVVbIE4LGAjsauvlhjfl0MYacxClLld1VrOw==}
837
+
'@rollup/rollup-linux-arm64-musl@4.53.3':
838
+
resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==}
650
839
cpu: [arm64]
651
840
os: [linux]
652
841
653
-
'@rollup/rollup-linux-loongarch64-gnu@4.32.1':
654
-
resolution: {integrity: sha512-F51qLdOtpS6P1zJVRzYM0v6MrBNypyPEN1GfMiz0gPu9jN8ScGaEFIZQwteSsGKg799oR5EaP7+B2jHgL+d+Kw==}
842
+
'@rollup/rollup-linux-loong64-gnu@4.53.3':
843
+
resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==}
655
844
cpu: [loong64]
656
845
os: [linux]
657
846
658
-
'@rollup/rollup-linux-powerpc64le-gnu@4.32.1':
659
-
resolution: {integrity: sha512-wO0WkfSppfX4YFm5KhdCCpnpGbtgQNj/tgvYzrVYFKDpven8w2N6Gg5nB6w+wAMO3AIfSTWeTjfVe+uZ23zAlg==}
847
+
'@rollup/rollup-linux-ppc64-gnu@4.53.3':
848
+
resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==}
660
849
cpu: [ppc64]
661
850
os: [linux]
662
851
663
-
'@rollup/rollup-linux-riscv64-gnu@4.32.1':
664
-
resolution: {integrity: sha512-iWswS9cIXfJO1MFYtI/4jjlrGb/V58oMu4dYJIKnR5UIwbkzR0PJ09O0PDZT0oJ3LYWXBSWahNf/Mjo6i1E5/g==}
852
+
'@rollup/rollup-linux-riscv64-gnu@4.53.3':
853
+
resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==}
665
854
cpu: [riscv64]
666
855
os: [linux]
667
856
668
-
'@rollup/rollup-linux-s390x-gnu@4.32.1':
669
-
resolution: {integrity: sha512-RKt8NI9tebzmEthMnfVgG3i/XeECkMPS+ibVZjZ6mNekpbbUmkNWuIN2yHsb/mBPyZke4nlI4YqIdFPgKuoyQQ==}
857
+
'@rollup/rollup-linux-riscv64-musl@4.53.3':
858
+
resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==}
859
+
cpu: [riscv64]
860
+
os: [linux]
861
+
862
+
'@rollup/rollup-linux-s390x-gnu@4.53.3':
863
+
resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==}
670
864
cpu: [s390x]
671
865
os: [linux]
672
866
673
-
'@rollup/rollup-linux-x64-gnu@4.32.1':
674
-
resolution: {integrity: sha512-WQFLZ9c42ECqEjwg/GHHsouij3pzLXkFdz0UxHa/0OM12LzvX7DzedlY0SIEly2v18YZLRhCRoHZDxbBSWoGYg==}
867
+
'@rollup/rollup-linux-x64-gnu@4.53.3':
868
+
resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==}
675
869
cpu: [x64]
676
870
os: [linux]
677
871
678
-
'@rollup/rollup-linux-x64-musl@4.32.1':
679
-
resolution: {integrity: sha512-BLoiyHDOWoS3uccNSADMza6V6vCNiphi94tQlVIL5de+r6r/CCQuNnerf+1g2mnk2b6edp5dk0nhdZ7aEjOBsA==}
872
+
'@rollup/rollup-linux-x64-musl@4.53.3':
873
+
resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==}
680
874
cpu: [x64]
681
875
os: [linux]
682
876
683
-
'@rollup/rollup-win32-arm64-msvc@4.32.1':
684
-
resolution: {integrity: sha512-w2l3UnlgYTNNU+Z6wOR8YdaioqfEnwPjIsJ66KxKAf0p+AuL2FHeTX6qvM+p/Ue3XPBVNyVSfCrfZiQh7vZHLQ==}
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==}
685
884
cpu: [arm64]
686
885
os: [win32]
687
886
688
-
'@rollup/rollup-win32-ia32-msvc@4.32.1':
689
-
resolution: {integrity: sha512-Am9H+TGLomPGkBnaPWie4F3x+yQ2rr4Bk2jpwy+iV+Gel9jLAu/KqT8k3X4jxFPW6Zf8OMnehyutsd+eHoq1WQ==}
887
+
'@rollup/rollup-win32-ia32-msvc@4.53.3':
888
+
resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==}
690
889
cpu: [ia32]
691
890
os: [win32]
692
891
693
-
'@rollup/rollup-win32-x64-msvc@4.32.1':
694
-
resolution: {integrity: sha512-ar80GhdZb4DgmW3myIS9nRFYcpJRSME8iqWgzH2i44u+IdrzmiXVxeFnExQ5v4JYUSpg94bWjevMG8JHf1Da5Q==}
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==}
695
899
cpu: [x64]
696
900
os: [win32]
697
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
+
698
912
'@tailwindcss/forms@0.5.10':
699
913
resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==}
700
914
peerDependencies:
···
703
917
'@types/babel__core@7.20.5':
704
918
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
705
919
706
-
'@types/babel__generator@7.6.8':
707
-
resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==}
920
+
'@types/babel__generator@7.27.0':
921
+
resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
708
922
709
923
'@types/babel__template@7.4.4':
710
924
resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
711
925
712
-
'@types/babel__traverse@7.20.6':
713
-
resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==}
926
+
'@types/babel__traverse@7.28.0':
927
+
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
714
928
715
-
'@types/estree@1.0.6':
716
-
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
929
+
'@types/estree@1.0.8':
930
+
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
717
931
718
-
'@types/node@22.12.0':
719
-
resolution: {integrity: sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==}
932
+
'@types/node@22.19.2':
933
+
resolution: {integrity: sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==}
720
934
721
-
acorn-walk@8.3.4:
722
-
resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
935
+
acorn-walk@8.3.2:
936
+
resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
723
937
engines: {node: '>=0.4.0'}
724
938
725
939
acorn@8.14.0:
···
727
941
engines: {node: '>=0.4.0'}
728
942
hasBin: true
729
943
730
-
ansi-regex@5.0.1:
731
-
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
732
-
engines: {node: '>=8'}
733
-
734
-
ansi-regex@6.1.0:
735
-
resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
736
-
engines: {node: '>=12'}
737
-
738
-
ansi-styles@4.3.0:
739
-
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
740
-
engines: {node: '>=8'}
741
-
742
-
ansi-styles@6.2.1:
743
-
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
744
-
engines: {node: '>=12'}
944
+
acorn@8.15.0:
945
+
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
946
+
engines: {node: '>=0.4.0'}
947
+
hasBin: true
745
948
746
949
any-promise@1.3.0:
747
950
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
···
753
956
arg@5.0.2:
754
957
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
755
958
756
-
as-table@1.0.55:
757
-
resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==}
758
-
759
-
autoprefixer@10.4.20:
760
-
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
959
+
autoprefixer@10.4.22:
960
+
resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==}
761
961
engines: {node: ^10 || ^12 || >=14}
762
962
hasBin: true
763
963
peerDependencies:
764
964
postcss: ^8.1.0
765
965
766
-
babel-plugin-jsx-dom-expressions@0.39.6:
767
-
resolution: {integrity: sha512-HMkTn5A3NyydEgG7HKmm48YcnsQQyqeT6SKNWh2TrS6nn5rOLeHDfg5hPbrRUCFUqaT9WGn5NInQfMc3qne3Dg==}
966
+
babel-plugin-jsx-dom-expressions@0.40.3:
967
+
resolution: {integrity: sha512-5HOwwt0BYiv/zxl7j8Pf2bGL6rDXfV6nUhLs8ygBX+EFJXzBPHM/euj9j/6deMZ6wa52Wb2PBaAV5U/jKwIY1w==}
768
968
peerDependencies:
769
969
'@babel/core': ^7.20.12
770
970
771
-
babel-preset-solid@1.9.3:
772
-
resolution: {integrity: sha512-jvlx5wDp8s+bEF9sGFw/84SInXOA51ttkUEroQziKMbxplXThVKt83qB6bDTa1HuLNatdU9FHpFOiQWs1tLQIg==}
971
+
babel-preset-solid@1.9.10:
972
+
resolution: {integrity: sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ==}
773
973
peerDependencies:
774
974
'@babel/core': ^7.0.0
975
+
solid-js: ^1.9.10
976
+
peerDependenciesMeta:
977
+
solid-js:
978
+
optional: true
775
979
776
-
balanced-match@1.0.2:
777
-
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
980
+
baseline-browser-mapping@2.9.5:
981
+
resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==}
982
+
hasBin: true
778
983
779
984
binary-extensions@2.3.0:
780
985
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
···
783
988
blake3-wasm@2.1.5:
784
989
resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
785
990
786
-
brace-expansion@2.0.1:
787
-
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
788
-
789
991
braces@3.0.3:
790
992
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
791
993
engines: {node: '>=8'}
792
994
793
-
browserslist@4.24.4:
794
-
resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==}
995
+
browserslist@4.28.1:
996
+
resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
795
997
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
796
998
hasBin: true
797
999
···
802
1004
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
803
1005
engines: {node: '>= 6'}
804
1006
805
-
caniuse-lite@1.0.30001696:
806
-
resolution: {integrity: sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==}
807
-
808
-
capnp-ts@0.7.0:
809
-
resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==}
1007
+
caniuse-lite@1.0.30001760:
1008
+
resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==}
810
1009
811
1010
chokidar@3.6.0:
812
1011
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
···
818
1017
819
1018
color-name@1.1.4:
820
1019
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
1020
+
1021
+
color-string@1.9.1:
1022
+
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
1023
+
1024
+
color@4.2.3:
1025
+
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
1026
+
engines: {node: '>=12.5.0'}
821
1027
822
1028
commander@2.20.3:
823
1029
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
···
826
1032
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
827
1033
engines: {node: '>= 6'}
828
1034
829
-
confbox@0.1.8:
830
-
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
831
-
832
1035
convert-source-map@2.0.0:
833
1036
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
834
1037
835
-
cookie@0.7.2:
836
-
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
837
-
engines: {node: '>= 0.6'}
838
-
839
-
cross-spawn@7.0.6:
840
-
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
841
-
engines: {node: '>= 8'}
1038
+
cookie@1.1.1:
1039
+
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
1040
+
engines: {node: '>=18'}
842
1041
843
1042
cssesc@3.0.0:
844
1043
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
845
1044
engines: {node: '>=4'}
846
1045
hasBin: true
847
1046
848
-
csstype@3.1.3:
849
-
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
1047
+
csstype@3.2.3:
1048
+
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
850
1049
851
-
data-uri-to-buffer@2.0.2:
852
-
resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==}
853
-
854
-
debug@4.4.0:
855
-
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
1050
+
debug@4.4.3:
1051
+
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
856
1052
engines: {node: '>=6.0'}
857
1053
peerDependencies:
858
1054
supports-color: '*'
···
860
1056
supports-color:
861
1057
optional: true
862
1058
863
-
defu@6.1.4:
864
-
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
1059
+
detect-libc@2.1.2:
1060
+
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
1061
+
engines: {node: '>=8'}
865
1062
866
1063
didyoumean@1.2.2:
867
1064
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
···
869
1066
dlv@1.1.3:
870
1067
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
871
1068
872
-
eastasianwidth@0.2.0:
873
-
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
874
-
875
-
electron-to-chromium@1.5.90:
876
-
resolution: {integrity: sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==}
877
-
878
-
emoji-regex@8.0.0:
879
-
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
880
-
881
-
emoji-regex@9.2.2:
882
-
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
1069
+
electron-to-chromium@1.5.267:
1070
+
resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==}
883
1071
884
-
entities@4.5.0:
885
-
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
1072
+
entities@6.0.1:
1073
+
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
886
1074
engines: {node: '>=0.12'}
887
1075
888
-
esbuild@0.17.19:
889
-
resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
890
-
engines: {node: '>=12'}
1076
+
error-stack-parser-es@1.0.5:
1077
+
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
1078
+
1079
+
esbuild@0.25.12:
1080
+
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
1081
+
engines: {node: '>=18'}
891
1082
hasBin: true
892
1083
893
-
esbuild@0.24.2:
894
-
resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==}
1084
+
esbuild@0.27.0:
1085
+
resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==}
895
1086
engines: {node: '>=18'}
896
1087
hasBin: true
897
1088
···
899
1090
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
900
1091
engines: {node: '>=6'}
901
1092
902
-
escape-string-regexp@4.0.0:
903
-
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
904
-
engines: {node: '>=10'}
905
-
906
-
estree-walker@0.6.1:
907
-
resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==}
1093
+
esm-env@1.2.2:
1094
+
resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
908
1095
909
1096
exit-hook@2.2.1:
910
1097
resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==}
···
914
1101
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
915
1102
engines: {node: '>=8.6.0'}
916
1103
917
-
fastq@1.18.0:
918
-
resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==}
1104
+
fastq@1.19.1:
1105
+
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
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
919
1115
920
1116
fetch-blob@3.2.0:
921
1117
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
···
925
1121
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
926
1122
engines: {node: '>=8'}
927
1123
928
-
foreground-child@3.3.0:
929
-
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
930
-
engines: {node: '>=14'}
931
-
932
-
fraction.js@4.3.7:
933
-
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
1124
+
fraction.js@5.3.4:
1125
+
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
934
1126
935
1127
fsevents@2.3.3:
936
1128
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
···
943
1135
gensync@1.0.0-beta.2:
944
1136
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
945
1137
engines: {node: '>=6.9.0'}
946
-
947
-
get-source@2.0.12:
948
-
resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==}
949
1138
950
1139
glob-parent@5.1.2:
951
1140
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
···
958
1147
glob-to-regexp@0.4.1:
959
1148
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
960
1149
961
-
glob@10.4.5:
962
-
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
963
-
hasBin: true
964
-
965
-
globals@11.12.0:
966
-
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
967
-
engines: {node: '>=4'}
968
-
969
1150
hasown@2.0.2:
970
1151
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
971
1152
engines: {node: '>= 0.4'}
···
973
1154
html-entities@2.3.3:
974
1155
resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
975
1156
1157
+
is-arrayish@0.3.4:
1158
+
resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==}
1159
+
976
1160
is-binary-path@2.1.0:
977
1161
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
978
1162
engines: {node: '>=8'}
···
985
1169
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
986
1170
engines: {node: '>=0.10.0'}
987
1171
988
-
is-fullwidth-code-point@3.0.0:
989
-
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
990
-
engines: {node: '>=8'}
991
-
992
1172
is-glob@4.0.3:
993
1173
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
994
1174
engines: {node: '>=0.10.0'}
···
1000
1180
is-what@4.1.16:
1001
1181
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
1002
1182
engines: {node: '>=12.13'}
1003
-
1004
-
isexe@2.0.0:
1005
-
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
1006
-
1007
-
jackspeak@3.4.3:
1008
-
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
1009
1183
1010
1184
jiti@1.21.7:
1011
1185
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
···
1024
1198
engines: {node: '>=6'}
1025
1199
hasBin: true
1026
1200
1201
+
kleur@4.1.5:
1202
+
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
1203
+
engines: {node: '>=6'}
1204
+
1027
1205
lilconfig@3.1.3:
1028
1206
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
1029
1207
engines: {node: '>=14'}
···
1031
1209
lines-and-columns@1.2.4:
1032
1210
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
1033
1211
1034
-
lru-cache@10.4.3:
1035
-
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
1036
-
1037
1212
lru-cache@5.1.1:
1038
1213
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
1039
-
1040
-
magic-string@0.25.9:
1041
-
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
1042
1214
1043
1215
merge-anything@5.1.7:
1044
1216
resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==}
···
1061
1233
resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
1062
1234
hasBin: true
1063
1235
1064
-
miniflare@3.20250124.0:
1065
-
resolution: {integrity: sha512-ewsetUwhj4FqeLoE3UMqYHHyCYIOPzdhlpF9CHuHpMZbfLvI9SPd+VrKrLfOgyAF97EHqVWb6WamIrLdgtj6Kg==}
1066
-
engines: {node: '>=16.13'}
1236
+
miniflare@4.20251202.1:
1237
+
resolution: {integrity: sha512-cRp2QNgnt9wpLMoNs4MOzzomyfe9UTS9sPRxIpUvxMl+mweCZ0FHpWWQvCnU7wWlfAP8VGZrHwqSsV5ERA6ahQ==}
1238
+
engines: {node: '>=18.0.0'}
1067
1239
hasBin: true
1068
1240
1069
-
minimatch@9.0.5:
1070
-
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
1071
-
engines: {node: '>=16 || 14 >=14.17'}
1072
-
1073
-
minipass@7.1.2:
1074
-
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
1075
-
engines: {node: '>=16 || 14 >=14.17'}
1076
-
1077
-
mlly@1.7.4:
1078
-
resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==}
1079
-
1080
1241
ms@2.1.3:
1081
1242
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
1082
1243
1083
-
mustache@4.2.0:
1084
-
resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
1085
-
hasBin: true
1086
-
1087
1244
mz@2.7.0:
1088
1245
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
1089
1246
1090
-
nanoid@3.3.8:
1091
-
resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
1247
+
nanoid@3.3.11:
1248
+
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
1092
1249
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
1093
1250
hasBin: true
1094
1251
1095
-
nanoid@5.0.9:
1096
-
resolution: {integrity: sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==}
1252
+
nanoid@5.1.6:
1253
+
resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==}
1097
1254
engines: {node: ^18 || >=20}
1098
1255
hasBin: true
1099
1256
···
1104
1261
node-domexception@1.0.0:
1105
1262
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
1106
1263
engines: {node: '>=10.5.0'}
1264
+
deprecated: Use your platform's native DOMException instead
1107
1265
1108
-
node-releases@2.0.19:
1109
-
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
1266
+
node-releases@2.0.27:
1267
+
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
1110
1268
1111
1269
normalize-path@3.0.0:
1112
1270
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
···
1124
1282
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
1125
1283
engines: {node: '>= 6'}
1126
1284
1127
-
ohash@1.1.4:
1128
-
resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==}
1129
-
1130
-
package-json-from-dist@1.0.1:
1131
-
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
1132
-
1133
-
parse5@7.2.1:
1134
-
resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==}
1135
-
1136
-
path-key@3.1.1:
1137
-
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
1138
-
engines: {node: '>=8'}
1285
+
parse5@7.3.0:
1286
+
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
1139
1287
1140
1288
path-parse@1.0.7:
1141
1289
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
1142
1290
1143
-
path-scurry@1.11.1:
1144
-
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
1145
-
engines: {node: '>=16 || 14 >=14.18'}
1146
-
1147
1291
path-to-regexp@6.3.0:
1148
1292
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
1149
1293
1150
-
pathe@1.1.2:
1151
-
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
1152
-
1153
-
pathe@2.0.2:
1154
-
resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==}
1294
+
pathe@2.0.3:
1295
+
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
1155
1296
1156
1297
picocolors@1.1.1:
1157
1298
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
···
1160
1301
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
1161
1302
engines: {node: '>=8.6'}
1162
1303
1304
+
picomatch@4.0.3:
1305
+
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
1306
+
engines: {node: '>=12'}
1307
+
1163
1308
pify@2.3.0:
1164
1309
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
1165
1310
engines: {node: '>=0.10.0'}
1166
1311
1167
-
pirates@4.0.6:
1168
-
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
1312
+
pirates@4.0.7:
1313
+
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
1169
1314
engines: {node: '>= 6'}
1170
-
1171
-
pkg-types@1.3.1:
1172
-
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
1173
1315
1174
1316
postcss-import@15.1.0:
1175
1317
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
···
1177
1319
peerDependencies:
1178
1320
postcss: ^8.0.0
1179
1321
1180
-
postcss-js@4.0.1:
1181
-
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
1322
+
postcss-js@4.1.0:
1323
+
resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==}
1182
1324
engines: {node: ^12 || ^14 || >= 16}
1183
1325
peerDependencies:
1184
1326
postcss: ^8.4.21
1185
1327
1186
-
postcss-load-config@4.0.2:
1187
-
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
1188
-
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'}
1189
1331
peerDependencies:
1332
+
jiti: '>=1.21.0'
1190
1333
postcss: '>=8.0.9'
1191
-
ts-node: '>=9.0.0'
1334
+
tsx: ^4.8.1
1335
+
yaml: ^2.4.2
1192
1336
peerDependenciesMeta:
1337
+
jiti:
1338
+
optional: true
1193
1339
postcss:
1194
1340
optional: true
1195
-
ts-node:
1341
+
tsx:
1342
+
optional: true
1343
+
yaml:
1196
1344
optional: true
1197
1345
1198
1346
postcss-nested@6.2.0:
···
1208
1356
postcss-value-parser@4.2.0:
1209
1357
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
1210
1358
1211
-
postcss@8.5.1:
1212
-
resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==}
1359
+
postcss@8.5.6:
1360
+
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
1213
1361
engines: {node: ^10 || ^12 || >=14}
1214
1362
1215
-
prettier-plugin-tailwindcss@0.6.11:
1216
-
resolution: {integrity: sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==}
1363
+
prettier-plugin-tailwindcss@0.6.14:
1364
+
resolution: {integrity: sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==}
1217
1365
engines: {node: '>=14.21.3'}
1218
1366
peerDependencies:
1219
1367
'@ianvs/prettier-plugin-sort-imports': '*'
1368
+
'@prettier/plugin-hermes': '*'
1369
+
'@prettier/plugin-oxc': '*'
1220
1370
'@prettier/plugin-pug': '*'
1221
1371
'@shopify/prettier-plugin-liquid': '*'
1222
1372
'@trivago/prettier-plugin-sort-imports': '*'
···
1236
1386
peerDependenciesMeta:
1237
1387
'@ianvs/prettier-plugin-sort-imports':
1238
1388
optional: true
1389
+
'@prettier/plugin-hermes':
1390
+
optional: true
1391
+
'@prettier/plugin-oxc':
1392
+
optional: true
1239
1393
'@prettier/plugin-pug':
1240
1394
optional: true
1241
1395
'@shopify/prettier-plugin-liquid':
···
1267
1421
prettier-plugin-svelte:
1268
1422
optional: true
1269
1423
1270
-
prettier@3.4.2:
1271
-
resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==}
1424
+
prettier@3.7.4:
1425
+
resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==}
1272
1426
engines: {node: '>=14'}
1273
1427
hasBin: true
1274
-
1275
-
printable-characters@1.0.42:
1276
-
resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==}
1277
1428
1278
1429
queue-microtask@1.2.3:
1279
1430
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
···
1285
1436
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
1286
1437
engines: {node: '>=8.10.0'}
1287
1438
1288
-
resolve@1.22.10:
1289
-
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
1439
+
resolve@1.22.11:
1440
+
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
1290
1441
engines: {node: '>= 0.4'}
1291
1442
hasBin: true
1292
1443
1293
-
reusify@1.0.4:
1294
-
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
1444
+
reusify@1.1.0:
1445
+
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
1295
1446
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
1296
1447
1297
-
rollup-plugin-inject@3.0.2:
1298
-
resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==}
1299
-
deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.
1300
-
1301
-
rollup-plugin-node-polyfills@0.2.1:
1302
-
resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==}
1303
-
1304
-
rollup-pluginutils@2.8.2:
1305
-
resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
1306
-
1307
-
rollup@4.32.1:
1308
-
resolution: {integrity: sha512-z+aeEsOeEa3mEbS1Tjl6sAZ8NE3+AalQz1RJGj81M+fizusbdDMoEJwdJNHfaB40Scr4qNu+welOfes7maKonA==}
1448
+
rollup@4.53.3:
1449
+
resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==}
1309
1450
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
1310
1451
hasBin: true
1311
1452
···
1316
1457
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
1317
1458
hasBin: true
1318
1459
1319
-
seroval-plugins@1.2.0:
1320
-
resolution: {integrity: sha512-hULTbfzSe81jGWLH8TAJjkEvw6JWMqOo9Uq+4V4vg+HNq53hyHldM9ZOfjdzokcFysiTp9aFdV2vJpZFqKeDjQ==}
1460
+
semver@7.7.3:
1461
+
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
1462
+
engines: {node: '>=10'}
1463
+
hasBin: true
1464
+
1465
+
seroval-plugins@1.3.3:
1466
+
resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==}
1321
1467
engines: {node: '>=10'}
1322
1468
peerDependencies:
1323
1469
seroval: ^1.0
1324
1470
1325
-
seroval@1.2.0:
1326
-
resolution: {integrity: sha512-GURoU99ko2UiAgUC3qDCk59Jb3Ss4Po8VIMGkG8j5PFo2Q7y0YSMP8QG9NuL/fJCoTz9V1XZUbpNIMXPOfaGpA==}
1471
+
seroval@1.3.2:
1472
+
resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==}
1327
1473
engines: {node: '>=10'}
1328
1474
1329
-
shebang-command@2.0.0:
1330
-
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
1331
-
engines: {node: '>=8'}
1332
-
1333
-
shebang-regex@3.0.0:
1334
-
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
1335
-
engines: {node: '>=8'}
1475
+
sharp@0.33.5:
1476
+
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
1477
+
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
1336
1478
1337
-
signal-exit@4.1.0:
1338
-
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
1339
-
engines: {node: '>=14'}
1479
+
simple-swizzle@0.2.4:
1480
+
resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==}
1340
1481
1341
-
solid-js@1.9.4:
1342
-
resolution: {integrity: sha512-ipQl8FJ31bFUoBNScDQTG3BjN6+9Rg+Q+f10bUbnO6EOTTf5NGerJeHc7wyu5I4RMHEl/WwZwUmy/PTRgxxZ8g==}
1482
+
solid-js@1.9.10:
1483
+
resolution: {integrity: sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==}
1343
1484
1344
1485
solid-refresh@0.6.3:
1345
1486
resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==}
···
1356
1497
source-map@0.6.1:
1357
1498
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
1358
1499
engines: {node: '>=0.10.0'}
1359
-
1360
-
sourcemap-codec@1.4.8:
1361
-
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
1362
-
deprecated: Please use @jridgewell/sourcemap-codec instead
1363
-
1364
-
stacktracey@2.1.8:
1365
-
resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==}
1366
1500
1367
1501
stoppable@1.1.0:
1368
1502
resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==}
1369
1503
engines: {node: '>=4', npm: '>=6'}
1370
1504
1371
-
string-width@4.2.3:
1372
-
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
1373
-
engines: {node: '>=8'}
1374
-
1375
-
string-width@5.1.2:
1376
-
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
1377
-
engines: {node: '>=12'}
1378
-
1379
-
strip-ansi@6.0.1:
1380
-
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
1381
-
engines: {node: '>=8'}
1382
-
1383
-
strip-ansi@7.1.0:
1384
-
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
1385
-
engines: {node: '>=12'}
1386
-
1387
-
sucrase@3.35.0:
1388
-
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
1505
+
sucrase@3.35.1:
1506
+
resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
1389
1507
engines: {node: '>=16 || 14 >=14.17'}
1390
1508
hasBin: true
1391
1509
1510
+
supports-color@10.2.2:
1511
+
resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
1512
+
engines: {node: '>=18'}
1513
+
1392
1514
supports-preserve-symlinks-flag@1.0.0:
1393
1515
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
1394
1516
engines: {node: '>= 0.4'}
1395
1517
1396
-
tailwindcss@3.4.17:
1397
-
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
1518
+
tailwindcss@3.4.18:
1519
+
resolution: {integrity: sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==}
1398
1520
engines: {node: '>=14.0.0'}
1399
1521
hasBin: true
1400
1522
1401
-
terser@5.37.0:
1402
-
resolution: {integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==}
1523
+
terser@5.44.1:
1524
+
resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==}
1403
1525
engines: {node: '>=10'}
1404
1526
hasBin: true
1405
1527
···
1409
1531
1410
1532
thenify@3.3.1:
1411
1533
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
1534
+
1535
+
tinyglobby@0.2.15:
1536
+
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
1537
+
engines: {node: '>=12.0.0'}
1412
1538
1413
1539
to-regex-range@5.0.1:
1414
1540
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
···
1420
1546
tslib@2.8.1:
1421
1547
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
1422
1548
1423
-
typescript@5.7.2:
1424
-
resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==}
1549
+
typescript@5.9.3:
1550
+
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
1425
1551
engines: {node: '>=14.17'}
1426
1552
hasBin: true
1427
1553
1428
-
ufo@1.5.4:
1429
-
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
1554
+
undici-types@6.21.0:
1555
+
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
1430
1556
1431
-
undici-types@6.20.0:
1432
-
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'}
1433
1560
1434
-
undici@5.28.5:
1435
-
resolution: {integrity: sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==}
1436
-
engines: {node: '>=14.0'}
1561
+
unenv@2.0.0-rc.24:
1562
+
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
1437
1563
1438
-
unenv@2.0.0-rc.1:
1439
-
resolution: {integrity: sha512-PU5fb40H8X149s117aB4ytbORcCvlASdtF97tfls4BPIyj4PeVxvpSuy1jAptqYHqB0vb2w2sHvzM0XWcp2OKg==}
1440
-
1441
-
update-browserslist-db@1.1.2:
1442
-
resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==}
1564
+
update-browserslist-db@1.2.2:
1565
+
resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==}
1443
1566
hasBin: true
1444
1567
peerDependencies:
1445
1568
browserslist: '>= 4.21.0'
···
1447
1570
util-deprecate@1.0.2:
1448
1571
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
1449
1572
1450
-
validate-html-nesting@1.2.2:
1451
-
resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==}
1452
-
1453
-
vite-plugin-solid@2.11.0:
1454
-
resolution: {integrity: sha512-G+NiwDj4EAeUE0wt3Ur9f+Lt9oMUuLd0FIxYuqwJSqRacKQRteCwUFzNy8zMEt88xWokngQhiFjfJMhjc1fDXw==}
1573
+
vite-plugin-solid@2.11.10:
1574
+
resolution: {integrity: sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw==}
1455
1575
peerDependencies:
1456
1576
'@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.*
1457
1577
solid-js: ^1.7.2
1458
-
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
1459
1579
peerDependenciesMeta:
1460
1580
'@testing-library/jest-dom':
1461
1581
optional: true
1462
1582
1463
-
vite@6.0.11:
1464
-
resolution: {integrity: sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==}
1465
-
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}
1466
1586
hasBin: true
1467
1587
peerDependencies:
1468
-
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
1588
+
'@types/node': ^20.19.0 || >=22.12.0
1469
1589
jiti: '>=1.21.0'
1470
-
less: '*'
1590
+
less: ^4.0.0
1471
1591
lightningcss: ^1.21.0
1472
-
sass: '*'
1473
-
sass-embedded: '*'
1474
-
stylus: '*'
1475
-
sugarss: '*'
1592
+
sass: ^1.70.0
1593
+
sass-embedded: ^1.70.0
1594
+
stylus: '>=0.54.8'
1595
+
sugarss: ^5.0.0
1476
1596
terser: ^5.16.0
1477
1597
tsx: ^4.8.1
1478
1598
yaml: ^2.4.2
···
1500
1620
yaml:
1501
1621
optional: true
1502
1622
1503
-
vitefu@1.0.5:
1504
-
resolution: {integrity: sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==}
1623
+
vitefu@1.1.1:
1624
+
resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==}
1505
1625
peerDependencies:
1506
-
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
1507
1627
peerDependenciesMeta:
1508
1628
vite:
1509
1629
optional: true
···
1512
1632
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
1513
1633
engines: {node: '>= 8'}
1514
1634
1515
-
which@2.0.2:
1516
-
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
1517
-
engines: {node: '>= 8'}
1518
-
hasBin: true
1519
-
1520
-
workerd@1.20250124.0:
1521
-
resolution: {integrity: sha512-EnT9gN3M9/UHRFPZptKgK36DLOW8WfJV7cjNs3zstVbmF5cpFaHCAzX7tXWBO6zyvW/+EjklJPFtOvfatiZsuQ==}
1635
+
workerd@1.20251202.0:
1636
+
resolution: {integrity: sha512-p08YfrUMHkjCECNdT36r+6DpJIZX4kixbZ4n6GMUcLR5Gh18fakSCsiQrh72iOm4M9QHv/rM7P8YvCrUPWT5sg==}
1522
1637
engines: {node: '>=16'}
1523
1638
hasBin: true
1524
1639
1525
-
wrangler@3.106.0:
1526
-
resolution: {integrity: sha512-jKPBtASIdiihU9AJBJRvWktqdc2prTy41LUjMk6Sq6BCZePrDnS9VWhQWovoYojreSd+dKhU+ggL53fNKvifRg==}
1527
-
engines: {node: '>=16.17.0'}
1640
+
wrangler@4.53.0:
1641
+
resolution: {integrity: sha512-/wvnHlRnlHsqaeIgGbmcEJE5NFYdTUWHCKow+U5Tv2XwQXI9vXUqBwCLAGy/BwqyS5nnycRt2kppqCzgHgyb7Q==}
1642
+
engines: {node: '>=20.0.0'}
1528
1643
hasBin: true
1529
1644
peerDependencies:
1530
-
'@cloudflare/workers-types': ^4.20250121.0
1645
+
'@cloudflare/workers-types': ^4.20251202.0
1531
1646
peerDependenciesMeta:
1532
1647
'@cloudflare/workers-types':
1533
1648
optional: true
1534
1649
1535
-
wrap-ansi@7.0.0:
1536
-
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
1537
-
engines: {node: '>=10'}
1538
-
1539
-
wrap-ansi@8.1.0:
1540
-
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
1541
-
engines: {node: '>=12'}
1542
-
1543
1650
ws@8.18.0:
1544
1651
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
1545
1652
engines: {node: '>=10.0.0'}
···
1555
1662
yallist@3.1.1:
1556
1663
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
1557
1664
1558
-
yaml@2.7.0:
1559
-
resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==}
1560
-
engines: {node: '>= 14'}
1561
-
hasBin: true
1665
+
youch-core@0.3.3:
1666
+
resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==}
1562
1667
1563
-
youch@3.3.4:
1564
-
resolution: {integrity: sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==}
1668
+
youch@4.1.0-beta.10:
1669
+
resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==}
1565
1670
1566
-
zod@3.24.1:
1567
-
resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
1671
+
zod@3.22.3:
1672
+
resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==}
1568
1673
1569
1674
snapshots:
1570
1675
1571
1676
'@alloc/quick-lru@5.2.0': {}
1572
1677
1573
-
'@ampproject/remapping@2.3.0':
1678
+
'@atcute/atproto@3.1.9':
1679
+
dependencies:
1680
+
'@atcute/lexicons': 1.2.5
1681
+
1682
+
'@atcute/bluesky@3.2.13':
1683
+
dependencies:
1684
+
'@atcute/atproto': 3.1.9
1685
+
'@atcute/lexicons': 1.2.5
1686
+
1687
+
'@atcute/car@5.0.0':
1688
+
dependencies:
1689
+
'@atcute/cbor': 2.2.8
1690
+
'@atcute/cid': 2.2.6
1691
+
'@atcute/uint8array': 1.0.6
1692
+
'@atcute/varint': 1.0.3
1693
+
1694
+
'@atcute/cbor@2.2.8':
1695
+
dependencies:
1696
+
'@atcute/cid': 2.2.6
1697
+
'@atcute/multibase': 1.1.6
1698
+
'@atcute/uint8array': 1.0.6
1699
+
1700
+
'@atcute/cid@2.2.6':
1574
1701
dependencies:
1575
-
'@jridgewell/gen-mapping': 0.3.8
1576
-
'@jridgewell/trace-mapping': 0.3.25
1702
+
'@atcute/multibase': 1.1.6
1703
+
'@atcute/uint8array': 1.0.6
1577
1704
1578
-
'@atcute/bluesky@1.0.12(@atcute/client@2.0.7)':
1705
+
'@atcute/client@4.1.1':
1706
+
dependencies:
1707
+
'@atcute/identity': 1.1.3
1708
+
'@atcute/lexicons': 1.2.5
1709
+
1710
+
'@atcute/crypto@2.3.0':
1711
+
dependencies:
1712
+
'@atcute/multibase': 1.1.6
1713
+
'@atcute/uint8array': 1.0.6
1714
+
'@noble/secp256k1': 3.0.0
1715
+
1716
+
'@atcute/did-plc@0.2.0':
1579
1717
dependencies:
1580
-
'@atcute/client': 2.0.7
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
1581
1726
1582
-
'@atcute/car@2.0.1':
1727
+
'@atcute/identity-resolver@1.2.0(@atcute/identity@1.1.3)':
1583
1728
dependencies:
1584
-
'@atcute/cbor': 2.1.1
1585
-
'@atcute/cid': 2.1.0
1586
-
'@atcute/varint': 1.0.2
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
1587
1733
1588
-
'@atcute/cbor@2.1.1':
1734
+
'@atcute/identity@1.1.3':
1589
1735
dependencies:
1590
-
'@atcute/cid': 2.1.0
1591
-
'@atcute/multibase': 1.1.2
1592
-
'@atcute/uint8array': 1.0.1
1736
+
'@atcute/lexicons': 1.2.5
1737
+
'@badrap/valita': 0.4.6
1593
1738
1594
-
'@atcute/cid@2.1.0':
1739
+
'@atcute/lexicons@1.2.5':
1595
1740
dependencies:
1596
-
'@atcute/multibase': 1.1.2
1597
-
'@atcute/uint8array': 1.0.1
1598
-
'@atcute/varint': 1.0.2
1741
+
'@standard-schema/spec': 1.0.0
1742
+
esm-env: 1.2.2
1599
1743
1600
-
'@atcute/client@2.0.7': {}
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
1601
1749
1602
-
'@atcute/crypto@2.2.0':
1750
+
'@atcute/multibase@1.1.6':
1603
1751
dependencies:
1604
-
'@atcute/multibase': 1.1.2
1605
-
'@atcute/uint8array': 1.0.1
1606
-
'@noble/secp256k1': 2.2.3
1752
+
'@atcute/uint8array': 1.0.6
1607
1753
1608
-
'@atcute/multibase@1.1.2':
1754
+
'@atcute/repo@0.1.0':
1609
1755
dependencies:
1610
-
'@atcute/uint8array': 1.0.1
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
1611
1763
1612
-
'@atcute/uint8array@1.0.1': {}
1764
+
'@atcute/tid@1.0.3': {}
1613
1765
1614
-
'@atcute/varint@1.0.2': {}
1766
+
'@atcute/uint8array@1.0.6': {}
1615
1767
1616
-
'@babel/code-frame@7.26.2':
1768
+
'@atcute/util-fetch@1.0.4':
1617
1769
dependencies:
1618
-
'@babel/helper-validator-identifier': 7.25.9
1770
+
'@badrap/valita': 0.4.6
1771
+
1772
+
'@atcute/varint@1.0.3': {}
1773
+
1774
+
'@babel/code-frame@7.27.1':
1775
+
dependencies:
1776
+
'@babel/helper-validator-identifier': 7.28.5
1619
1777
js-tokens: 4.0.0
1620
1778
picocolors: 1.1.1
1621
1779
1622
-
'@babel/compat-data@7.26.5': {}
1780
+
'@babel/compat-data@7.28.5': {}
1623
1781
1624
-
'@babel/core@7.26.7':
1782
+
'@babel/core@7.28.5':
1625
1783
dependencies:
1626
-
'@ampproject/remapping': 2.3.0
1627
-
'@babel/code-frame': 7.26.2
1628
-
'@babel/generator': 7.26.5
1629
-
'@babel/helper-compilation-targets': 7.26.5
1630
-
'@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.7)
1631
-
'@babel/helpers': 7.26.7
1632
-
'@babel/parser': 7.26.7
1633
-
'@babel/template': 7.25.9
1634
-
'@babel/traverse': 7.26.7
1635
-
'@babel/types': 7.26.7
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
1636
1794
convert-source-map: 2.0.0
1637
-
debug: 4.4.0
1795
+
debug: 4.4.3
1638
1796
gensync: 1.0.0-beta.2
1639
1797
json5: 2.2.3
1640
1798
semver: 6.3.1
1641
1799
transitivePeerDependencies:
1642
1800
- supports-color
1643
1801
1644
-
'@babel/generator@7.26.5':
1802
+
'@babel/generator@7.28.5':
1645
1803
dependencies:
1646
-
'@babel/parser': 7.26.7
1647
-
'@babel/types': 7.26.7
1648
-
'@jridgewell/gen-mapping': 0.3.8
1649
-
'@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
1650
1808
jsesc: 3.1.0
1651
1809
1652
-
'@babel/helper-compilation-targets@7.26.5':
1810
+
'@babel/helper-compilation-targets@7.27.2':
1653
1811
dependencies:
1654
-
'@babel/compat-data': 7.26.5
1655
-
'@babel/helper-validator-option': 7.25.9
1656
-
browserslist: 4.24.4
1812
+
'@babel/compat-data': 7.28.5
1813
+
'@babel/helper-validator-option': 7.27.1
1814
+
browserslist: 4.28.1
1657
1815
lru-cache: 5.1.1
1658
1816
semver: 6.3.1
1817
+
1818
+
'@babel/helper-globals@7.28.0': {}
1659
1819
1660
1820
'@babel/helper-module-imports@7.18.6':
1661
1821
dependencies:
1662
-
'@babel/types': 7.26.7
1822
+
'@babel/types': 7.28.5
1663
1823
1664
-
'@babel/helper-module-imports@7.25.9':
1824
+
'@babel/helper-module-imports@7.27.1':
1665
1825
dependencies:
1666
-
'@babel/traverse': 7.26.7
1667
-
'@babel/types': 7.26.7
1826
+
'@babel/traverse': 7.28.5
1827
+
'@babel/types': 7.28.5
1668
1828
transitivePeerDependencies:
1669
1829
- supports-color
1670
1830
1671
-
'@babel/helper-module-transforms@7.26.0(@babel/core@7.26.7)':
1831
+
'@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)':
1672
1832
dependencies:
1673
-
'@babel/core': 7.26.7
1674
-
'@babel/helper-module-imports': 7.25.9
1675
-
'@babel/helper-validator-identifier': 7.25.9
1676
-
'@babel/traverse': 7.26.7
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
1677
1837
transitivePeerDependencies:
1678
1838
- supports-color
1679
1839
1680
-
'@babel/helper-plugin-utils@7.26.5': {}
1840
+
'@babel/helper-plugin-utils@7.27.1': {}
1681
1841
1682
-
'@babel/helper-string-parser@7.25.9': {}
1842
+
'@babel/helper-string-parser@7.27.1': {}
1683
1843
1684
-
'@babel/helper-validator-identifier@7.25.9': {}
1844
+
'@babel/helper-validator-identifier@7.28.5': {}
1685
1845
1686
-
'@babel/helper-validator-option@7.25.9': {}
1846
+
'@babel/helper-validator-option@7.27.1': {}
1687
1847
1688
-
'@babel/helpers@7.26.7':
1848
+
'@babel/helpers@7.28.4':
1689
1849
dependencies:
1690
-
'@babel/template': 7.25.9
1691
-
'@babel/types': 7.26.7
1850
+
'@babel/template': 7.27.2
1851
+
'@babel/types': 7.28.5
1692
1852
1693
-
'@babel/parser@7.26.7':
1853
+
'@babel/parser@7.28.5':
1694
1854
dependencies:
1695
-
'@babel/types': 7.26.7
1855
+
'@babel/types': 7.28.5
1696
1856
1697
-
'@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.7)':
1857
+
'@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)':
1698
1858
dependencies:
1699
-
'@babel/core': 7.26.7
1700
-
'@babel/helper-plugin-utils': 7.26.5
1859
+
'@babel/core': 7.28.5
1860
+
'@babel/helper-plugin-utils': 7.27.1
1701
1861
1702
-
'@babel/template@7.25.9':
1862
+
'@babel/template@7.27.2':
1703
1863
dependencies:
1704
-
'@babel/code-frame': 7.26.2
1705
-
'@babel/parser': 7.26.7
1706
-
'@babel/types': 7.26.7
1864
+
'@babel/code-frame': 7.27.1
1865
+
'@babel/parser': 7.28.5
1866
+
'@babel/types': 7.28.5
1707
1867
1708
-
'@babel/traverse@7.26.7':
1868
+
'@babel/traverse@7.28.5':
1709
1869
dependencies:
1710
-
'@babel/code-frame': 7.26.2
1711
-
'@babel/generator': 7.26.5
1712
-
'@babel/parser': 7.26.7
1713
-
'@babel/template': 7.25.9
1714
-
'@babel/types': 7.26.7
1715
-
debug: 4.4.0
1716
-
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
1717
1877
transitivePeerDependencies:
1718
1878
- supports-color
1719
1879
1720
-
'@babel/types@7.26.7':
1880
+
'@babel/types@7.28.5':
1721
1881
dependencies:
1722
-
'@babel/helper-string-parser': 7.25.9
1723
-
'@babel/helper-validator-identifier': 7.25.9
1882
+
'@babel/helper-string-parser': 7.27.1
1883
+
'@babel/helper-validator-identifier': 7.28.5
1724
1884
1725
-
'@badrap/valita@0.4.2': {}
1885
+
'@badrap/valita@0.4.6': {}
1726
1886
1727
-
'@cloudflare/kv-asset-handler@0.3.4':
1887
+
'@cloudflare/kv-asset-handler@0.4.1':
1728
1888
dependencies:
1729
1889
mime: 3.0.0
1730
1890
1731
-
'@cloudflare/workerd-darwin-64@1.20250124.0':
1891
+
'@cloudflare/unenv-preset@2.7.13(unenv@2.0.0-rc.24)(workerd@1.20251202.0)':
1892
+
dependencies:
1893
+
unenv: 2.0.0-rc.24
1894
+
optionalDependencies:
1895
+
workerd: 1.20251202.0
1896
+
1897
+
'@cloudflare/workerd-darwin-64@1.20251202.0':
1732
1898
optional: true
1733
1899
1734
-
'@cloudflare/workerd-darwin-arm64@1.20250124.0':
1900
+
'@cloudflare/workerd-darwin-arm64@1.20251202.0':
1735
1901
optional: true
1736
1902
1737
-
'@cloudflare/workerd-linux-64@1.20250124.0':
1903
+
'@cloudflare/workerd-linux-64@1.20251202.0':
1738
1904
optional: true
1739
1905
1740
-
'@cloudflare/workerd-linux-arm64@1.20250124.0':
1906
+
'@cloudflare/workerd-linux-arm64@1.20251202.0':
1741
1907
optional: true
1742
1908
1743
-
'@cloudflare/workerd-windows-64@1.20250124.0':
1909
+
'@cloudflare/workerd-windows-64@1.20251202.0':
1744
1910
optional: true
1745
1911
1746
1912
'@cspotcode/source-map-support@0.8.1':
1747
1913
dependencies:
1748
1914
'@jridgewell/trace-mapping': 0.3.9
1749
1915
1750
-
'@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19)':
1916
+
'@emnapi/runtime@1.7.1':
1751
1917
dependencies:
1752
-
esbuild: 0.17.19
1918
+
tslib: 2.8.1
1919
+
optional: true
1920
+
1921
+
'@esbuild/aix-ppc64@0.25.12':
1922
+
optional: true
1923
+
1924
+
'@esbuild/aix-ppc64@0.27.0':
1925
+
optional: true
1926
+
1927
+
'@esbuild/android-arm64@0.25.12':
1928
+
optional: true
1929
+
1930
+
'@esbuild/android-arm64@0.27.0':
1931
+
optional: true
1932
+
1933
+
'@esbuild/android-arm@0.25.12':
1934
+
optional: true
1935
+
1936
+
'@esbuild/android-arm@0.27.0':
1937
+
optional: true
1938
+
1939
+
'@esbuild/android-x64@0.25.12':
1940
+
optional: true
1941
+
1942
+
'@esbuild/android-x64@0.27.0':
1943
+
optional: true
1944
+
1945
+
'@esbuild/darwin-arm64@0.25.12':
1946
+
optional: true
1947
+
1948
+
'@esbuild/darwin-arm64@0.27.0':
1949
+
optional: true
1950
+
1951
+
'@esbuild/darwin-x64@0.25.12':
1952
+
optional: true
1753
1953
1754
-
'@esbuild-plugins/node-modules-polyfill@0.2.2(esbuild@0.17.19)':
1755
-
dependencies:
1756
-
esbuild: 0.17.19
1757
-
escape-string-regexp: 4.0.0
1758
-
rollup-plugin-node-polyfills: 0.2.1
1954
+
'@esbuild/darwin-x64@0.27.0':
1955
+
optional: true
1759
1956
1760
-
'@esbuild/aix-ppc64@0.24.2':
1957
+
'@esbuild/freebsd-arm64@0.25.12':
1761
1958
optional: true
1762
1959
1763
-
'@esbuild/android-arm64@0.17.19':
1960
+
'@esbuild/freebsd-arm64@0.27.0':
1764
1961
optional: true
1765
1962
1766
-
'@esbuild/android-arm64@0.24.2':
1963
+
'@esbuild/freebsd-x64@0.25.12':
1767
1964
optional: true
1768
1965
1769
-
'@esbuild/android-arm@0.17.19':
1966
+
'@esbuild/freebsd-x64@0.27.0':
1770
1967
optional: true
1771
1968
1772
-
'@esbuild/android-arm@0.24.2':
1969
+
'@esbuild/linux-arm64@0.25.12':
1773
1970
optional: true
1774
1971
1775
-
'@esbuild/android-x64@0.17.19':
1972
+
'@esbuild/linux-arm64@0.27.0':
1776
1973
optional: true
1777
1974
1778
-
'@esbuild/android-x64@0.24.2':
1975
+
'@esbuild/linux-arm@0.25.12':
1779
1976
optional: true
1780
1977
1781
-
'@esbuild/darwin-arm64@0.17.19':
1978
+
'@esbuild/linux-arm@0.27.0':
1782
1979
optional: true
1783
1980
1784
-
'@esbuild/darwin-arm64@0.24.2':
1981
+
'@esbuild/linux-ia32@0.25.12':
1785
1982
optional: true
1786
1983
1787
-
'@esbuild/darwin-x64@0.17.19':
1984
+
'@esbuild/linux-ia32@0.27.0':
1788
1985
optional: true
1789
1986
1790
-
'@esbuild/darwin-x64@0.24.2':
1987
+
'@esbuild/linux-loong64@0.25.12':
1791
1988
optional: true
1792
1989
1793
-
'@esbuild/freebsd-arm64@0.17.19':
1990
+
'@esbuild/linux-loong64@0.27.0':
1794
1991
optional: true
1795
1992
1796
-
'@esbuild/freebsd-arm64@0.24.2':
1993
+
'@esbuild/linux-mips64el@0.25.12':
1797
1994
optional: true
1798
1995
1799
-
'@esbuild/freebsd-x64@0.17.19':
1996
+
'@esbuild/linux-mips64el@0.27.0':
1800
1997
optional: true
1801
1998
1802
-
'@esbuild/freebsd-x64@0.24.2':
1999
+
'@esbuild/linux-ppc64@0.25.12':
1803
2000
optional: true
1804
2001
1805
-
'@esbuild/linux-arm64@0.17.19':
2002
+
'@esbuild/linux-ppc64@0.27.0':
1806
2003
optional: true
1807
2004
1808
-
'@esbuild/linux-arm64@0.24.2':
2005
+
'@esbuild/linux-riscv64@0.25.12':
1809
2006
optional: true
1810
2007
1811
-
'@esbuild/linux-arm@0.17.19':
2008
+
'@esbuild/linux-riscv64@0.27.0':
2009
+
optional: true
2010
+
2011
+
'@esbuild/linux-s390x@0.25.12':
2012
+
optional: true
2013
+
2014
+
'@esbuild/linux-s390x@0.27.0':
2015
+
optional: true
2016
+
2017
+
'@esbuild/linux-x64@0.25.12':
2018
+
optional: true
2019
+
2020
+
'@esbuild/linux-x64@0.27.0':
2021
+
optional: true
2022
+
2023
+
'@esbuild/netbsd-arm64@0.25.12':
2024
+
optional: true
2025
+
2026
+
'@esbuild/netbsd-arm64@0.27.0':
2027
+
optional: true
2028
+
2029
+
'@esbuild/netbsd-x64@0.25.12':
1812
2030
optional: true
1813
2031
1814
-
'@esbuild/linux-arm@0.24.2':
2032
+
'@esbuild/netbsd-x64@0.27.0':
1815
2033
optional: true
1816
2034
1817
-
'@esbuild/linux-ia32@0.17.19':
2035
+
'@esbuild/openbsd-arm64@0.25.12':
1818
2036
optional: true
1819
2037
1820
-
'@esbuild/linux-ia32@0.24.2':
2038
+
'@esbuild/openbsd-arm64@0.27.0':
1821
2039
optional: true
1822
2040
1823
-
'@esbuild/linux-loong64@0.17.19':
2041
+
'@esbuild/openbsd-x64@0.25.12':
1824
2042
optional: true
1825
2043
1826
-
'@esbuild/linux-loong64@0.24.2':
2044
+
'@esbuild/openbsd-x64@0.27.0':
1827
2045
optional: true
1828
2046
1829
-
'@esbuild/linux-mips64el@0.17.19':
2047
+
'@esbuild/openharmony-arm64@0.25.12':
2048
+
optional: true
2049
+
2050
+
'@esbuild/openharmony-arm64@0.27.0':
2051
+
optional: true
2052
+
2053
+
'@esbuild/sunos-x64@0.25.12':
1830
2054
optional: true
1831
2055
1832
-
'@esbuild/linux-mips64el@0.24.2':
2056
+
'@esbuild/sunos-x64@0.27.0':
1833
2057
optional: true
1834
2058
1835
-
'@esbuild/linux-ppc64@0.17.19':
2059
+
'@esbuild/win32-arm64@0.25.12':
1836
2060
optional: true
1837
2061
1838
-
'@esbuild/linux-ppc64@0.24.2':
2062
+
'@esbuild/win32-arm64@0.27.0':
1839
2063
optional: true
1840
2064
1841
-
'@esbuild/linux-riscv64@0.17.19':
2065
+
'@esbuild/win32-ia32@0.25.12':
1842
2066
optional: true
1843
2067
1844
-
'@esbuild/linux-riscv64@0.24.2':
2068
+
'@esbuild/win32-ia32@0.27.0':
1845
2069
optional: true
1846
2070
1847
-
'@esbuild/linux-s390x@0.17.19':
2071
+
'@esbuild/win32-x64@0.25.12':
1848
2072
optional: true
1849
2073
1850
-
'@esbuild/linux-s390x@0.24.2':
2074
+
'@esbuild/win32-x64@0.27.0':
1851
2075
optional: true
1852
2076
1853
-
'@esbuild/linux-x64@0.17.19':
2077
+
'@externdefs/solid-freeze@0.1.1(solid-js@1.9.10)':
2078
+
dependencies:
2079
+
solid-js: 1.9.10
2080
+
2081
+
'@img/sharp-darwin-arm64@0.33.5':
2082
+
optionalDependencies:
2083
+
'@img/sharp-libvips-darwin-arm64': 1.0.4
1854
2084
optional: true
1855
2085
1856
-
'@esbuild/linux-x64@0.24.2':
2086
+
'@img/sharp-darwin-x64@0.33.5':
2087
+
optionalDependencies:
2088
+
'@img/sharp-libvips-darwin-x64': 1.0.4
1857
2089
optional: true
1858
2090
1859
-
'@esbuild/netbsd-arm64@0.24.2':
2091
+
'@img/sharp-libvips-darwin-arm64@1.0.4':
1860
2092
optional: true
1861
2093
1862
-
'@esbuild/netbsd-x64@0.17.19':
2094
+
'@img/sharp-libvips-darwin-x64@1.0.4':
1863
2095
optional: true
1864
2096
1865
-
'@esbuild/netbsd-x64@0.24.2':
2097
+
'@img/sharp-libvips-linux-arm64@1.0.4':
1866
2098
optional: true
1867
2099
1868
-
'@esbuild/openbsd-arm64@0.24.2':
2100
+
'@img/sharp-libvips-linux-arm@1.0.5':
1869
2101
optional: true
1870
2102
1871
-
'@esbuild/openbsd-x64@0.17.19':
2103
+
'@img/sharp-libvips-linux-s390x@1.0.4':
1872
2104
optional: true
1873
2105
1874
-
'@esbuild/openbsd-x64@0.24.2':
2106
+
'@img/sharp-libvips-linux-x64@1.0.4':
1875
2107
optional: true
1876
2108
1877
-
'@esbuild/sunos-x64@0.17.19':
2109
+
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
1878
2110
optional: true
1879
2111
1880
-
'@esbuild/sunos-x64@0.24.2':
2112
+
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
1881
2113
optional: true
1882
2114
1883
-
'@esbuild/win32-arm64@0.17.19':
2115
+
'@img/sharp-linux-arm64@0.33.5':
2116
+
optionalDependencies:
2117
+
'@img/sharp-libvips-linux-arm64': 1.0.4
1884
2118
optional: true
1885
2119
1886
-
'@esbuild/win32-arm64@0.24.2':
2120
+
'@img/sharp-linux-arm@0.33.5':
2121
+
optionalDependencies:
2122
+
'@img/sharp-libvips-linux-arm': 1.0.5
1887
2123
optional: true
1888
2124
1889
-
'@esbuild/win32-ia32@0.17.19':
2125
+
'@img/sharp-linux-s390x@0.33.5':
2126
+
optionalDependencies:
2127
+
'@img/sharp-libvips-linux-s390x': 1.0.4
1890
2128
optional: true
1891
2129
1892
-
'@esbuild/win32-ia32@0.24.2':
2130
+
'@img/sharp-linux-x64@0.33.5':
2131
+
optionalDependencies:
2132
+
'@img/sharp-libvips-linux-x64': 1.0.4
1893
2133
optional: true
1894
2134
1895
-
'@esbuild/win32-x64@0.17.19':
2135
+
'@img/sharp-linuxmusl-arm64@0.33.5':
2136
+
optionalDependencies:
2137
+
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
1896
2138
optional: true
1897
2139
1898
-
'@esbuild/win32-x64@0.24.2':
2140
+
'@img/sharp-linuxmusl-x64@0.33.5':
2141
+
optionalDependencies:
2142
+
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
1899
2143
optional: true
1900
2144
1901
-
'@externdefs/solid-freeze@0.1.1(solid-js@1.9.4)':
2145
+
'@img/sharp-wasm32@0.33.5':
1902
2146
dependencies:
1903
-
solid-js: 1.9.4
2147
+
'@emnapi/runtime': 1.7.1
2148
+
optional: true
1904
2149
1905
-
'@fastify/busboy@2.1.1': {}
2150
+
'@img/sharp-win32-ia32@0.33.5':
2151
+
optional: true
1906
2152
1907
-
'@isaacs/cliui@8.0.2':
2153
+
'@img/sharp-win32-x64@0.33.5':
2154
+
optional: true
2155
+
2156
+
'@jridgewell/gen-mapping@0.3.13':
1908
2157
dependencies:
1909
-
string-width: 5.1.2
1910
-
string-width-cjs: string-width@4.2.3
1911
-
strip-ansi: 7.1.0
1912
-
strip-ansi-cjs: strip-ansi@6.0.1
1913
-
wrap-ansi: 8.1.0
1914
-
wrap-ansi-cjs: wrap-ansi@7.0.0
2158
+
'@jridgewell/sourcemap-codec': 1.5.5
2159
+
'@jridgewell/trace-mapping': 0.3.31
1915
2160
1916
-
'@jridgewell/gen-mapping@0.3.8':
2161
+
'@jridgewell/remapping@2.3.5':
1917
2162
dependencies:
1918
-
'@jridgewell/set-array': 1.2.1
1919
-
'@jridgewell/sourcemap-codec': 1.5.0
1920
-
'@jridgewell/trace-mapping': 0.3.25
2163
+
'@jridgewell/gen-mapping': 0.3.13
2164
+
'@jridgewell/trace-mapping': 0.3.31
1921
2165
1922
2166
'@jridgewell/resolve-uri@3.1.2': {}
1923
2167
1924
-
'@jridgewell/set-array@1.2.1': {}
1925
-
1926
-
'@jridgewell/source-map@0.3.6':
2168
+
'@jridgewell/source-map@0.3.11':
1927
2169
dependencies:
1928
-
'@jridgewell/gen-mapping': 0.3.8
1929
-
'@jridgewell/trace-mapping': 0.3.25
2170
+
'@jridgewell/gen-mapping': 0.3.13
2171
+
'@jridgewell/trace-mapping': 0.3.31
1930
2172
1931
-
'@jridgewell/sourcemap-codec@1.5.0': {}
2173
+
'@jridgewell/sourcemap-codec@1.5.5': {}
1932
2174
1933
-
'@jridgewell/trace-mapping@0.3.25':
2175
+
'@jridgewell/trace-mapping@0.3.31':
1934
2176
dependencies:
1935
2177
'@jridgewell/resolve-uri': 3.1.2
1936
-
'@jridgewell/sourcemap-codec': 1.5.0
2178
+
'@jridgewell/sourcemap-codec': 1.5.5
1937
2179
1938
2180
'@jridgewell/trace-mapping@0.3.9':
1939
2181
dependencies:
1940
2182
'@jridgewell/resolve-uri': 3.1.2
1941
-
'@jridgewell/sourcemap-codec': 1.5.0
2183
+
'@jridgewell/sourcemap-codec': 1.5.5
1942
2184
1943
-
'@jsr/mary__array-fns@0.1.0': {}
2185
+
'@jsr/mary__array-fns@0.1.5': {}
2186
+
2187
+
'@jsr/mary__ds-queue@0.1.3': {}
1944
2188
1945
-
'@jsr/mary__events@0.1.0': {}
2189
+
'@jsr/mary__events@0.2.0': {}
1946
2190
1947
-
'@jsr/mary__tar@0.2.4': {}
2191
+
'@jsr/mary__tar@0.3.1': {}
1948
2192
1949
-
'@noble/secp256k1@2.2.3': {}
2193
+
'@noble/secp256k1@3.0.0': {}
1950
2194
1951
2195
'@nodelib/fs.scandir@2.1.5':
1952
2196
dependencies:
···
1958
2202
'@nodelib/fs.walk@1.2.8':
1959
2203
dependencies:
1960
2204
'@nodelib/fs.scandir': 2.1.5
1961
-
fastq: 1.18.0
2205
+
fastq: 1.19.1
1962
2206
1963
-
'@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':
1964
2220
optional: true
1965
2221
1966
-
'@rollup/rollup-android-arm-eabi@4.32.1':
2222
+
'@rollup/rollup-android-arm64@4.53.3':
1967
2223
optional: true
1968
2224
1969
-
'@rollup/rollup-android-arm64@4.32.1':
2225
+
'@rollup/rollup-darwin-arm64@4.53.3':
1970
2226
optional: true
1971
2227
1972
-
'@rollup/rollup-darwin-arm64@4.32.1':
2228
+
'@rollup/rollup-darwin-x64@4.53.3':
1973
2229
optional: true
1974
2230
1975
-
'@rollup/rollup-darwin-x64@4.32.1':
2231
+
'@rollup/rollup-freebsd-arm64@4.53.3':
1976
2232
optional: true
1977
2233
1978
-
'@rollup/rollup-freebsd-arm64@4.32.1':
2234
+
'@rollup/rollup-freebsd-x64@4.53.3':
1979
2235
optional: true
1980
2236
1981
-
'@rollup/rollup-freebsd-x64@4.32.1':
2237
+
'@rollup/rollup-linux-arm-gnueabihf@4.53.3':
1982
2238
optional: true
1983
2239
1984
-
'@rollup/rollup-linux-arm-gnueabihf@4.32.1':
2240
+
'@rollup/rollup-linux-arm-musleabihf@4.53.3':
1985
2241
optional: true
1986
2242
1987
-
'@rollup/rollup-linux-arm-musleabihf@4.32.1':
2243
+
'@rollup/rollup-linux-arm64-gnu@4.53.3':
1988
2244
optional: true
1989
2245
1990
-
'@rollup/rollup-linux-arm64-gnu@4.32.1':
2246
+
'@rollup/rollup-linux-arm64-musl@4.53.3':
1991
2247
optional: true
1992
2248
1993
-
'@rollup/rollup-linux-arm64-musl@4.32.1':
2249
+
'@rollup/rollup-linux-loong64-gnu@4.53.3':
1994
2250
optional: true
1995
2251
1996
-
'@rollup/rollup-linux-loongarch64-gnu@4.32.1':
2252
+
'@rollup/rollup-linux-ppc64-gnu@4.53.3':
1997
2253
optional: true
1998
2254
1999
-
'@rollup/rollup-linux-powerpc64le-gnu@4.32.1':
2255
+
'@rollup/rollup-linux-riscv64-gnu@4.53.3':
2000
2256
optional: true
2001
2257
2002
-
'@rollup/rollup-linux-riscv64-gnu@4.32.1':
2258
+
'@rollup/rollup-linux-riscv64-musl@4.53.3':
2003
2259
optional: true
2004
2260
2005
-
'@rollup/rollup-linux-s390x-gnu@4.32.1':
2261
+
'@rollup/rollup-linux-s390x-gnu@4.53.3':
2262
+
optional: true
2263
+
2264
+
'@rollup/rollup-linux-x64-gnu@4.53.3':
2265
+
optional: true
2266
+
2267
+
'@rollup/rollup-linux-x64-musl@4.53.3':
2006
2268
optional: true
2007
2269
2008
-
'@rollup/rollup-linux-x64-gnu@4.32.1':
2270
+
'@rollup/rollup-openharmony-arm64@4.53.3':
2009
2271
optional: true
2010
2272
2011
-
'@rollup/rollup-linux-x64-musl@4.32.1':
2273
+
'@rollup/rollup-win32-arm64-msvc@4.53.3':
2012
2274
optional: true
2013
2275
2014
-
'@rollup/rollup-win32-arm64-msvc@4.32.1':
2276
+
'@rollup/rollup-win32-ia32-msvc@4.53.3':
2015
2277
optional: true
2016
2278
2017
-
'@rollup/rollup-win32-ia32-msvc@4.32.1':
2279
+
'@rollup/rollup-win32-x64-gnu@4.53.3':
2018
2280
optional: true
2019
2281
2020
-
'@rollup/rollup-win32-x64-msvc@4.32.1':
2282
+
'@rollup/rollup-win32-x64-msvc@4.53.3':
2021
2283
optional: true
2022
2284
2023
-
'@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)':
2024
2292
dependencies:
2025
2293
mini-svg-data-uri: 1.4.4
2026
-
tailwindcss: 3.4.17
2294
+
tailwindcss: 3.4.18
2027
2295
2028
2296
'@types/babel__core@7.20.5':
2029
2297
dependencies:
2030
-
'@babel/parser': 7.26.7
2031
-
'@babel/types': 7.26.7
2032
-
'@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
2033
2301
'@types/babel__template': 7.4.4
2034
-
'@types/babel__traverse': 7.20.6
2302
+
'@types/babel__traverse': 7.28.0
2035
2303
2036
-
'@types/babel__generator@7.6.8':
2304
+
'@types/babel__generator@7.27.0':
2037
2305
dependencies:
2038
-
'@babel/types': 7.26.7
2306
+
'@babel/types': 7.28.5
2039
2307
2040
2308
'@types/babel__template@7.4.4':
2041
2309
dependencies:
2042
-
'@babel/parser': 7.26.7
2043
-
'@babel/types': 7.26.7
2310
+
'@babel/parser': 7.28.5
2311
+
'@babel/types': 7.28.5
2044
2312
2045
-
'@types/babel__traverse@7.20.6':
2313
+
'@types/babel__traverse@7.28.0':
2046
2314
dependencies:
2047
-
'@babel/types': 7.26.7
2315
+
'@babel/types': 7.28.5
2048
2316
2049
-
'@types/estree@1.0.6': {}
2317
+
'@types/estree@1.0.8': {}
2050
2318
2051
-
'@types/node@22.12.0':
2319
+
'@types/node@22.19.2':
2052
2320
dependencies:
2053
-
undici-types: 6.20.0
2321
+
undici-types: 6.21.0
2054
2322
2055
-
acorn-walk@8.3.4:
2056
-
dependencies:
2057
-
acorn: 8.14.0
2323
+
acorn-walk@8.3.2: {}
2058
2324
2059
2325
acorn@8.14.0: {}
2060
2326
2061
-
ansi-regex@5.0.1: {}
2062
-
2063
-
ansi-regex@6.1.0: {}
2064
-
2065
-
ansi-styles@4.3.0:
2066
-
dependencies:
2067
-
color-convert: 2.0.1
2068
-
2069
-
ansi-styles@6.2.1: {}
2327
+
acorn@8.15.0: {}
2070
2328
2071
2329
any-promise@1.3.0: {}
2072
2330
···
2077
2335
2078
2336
arg@5.0.2: {}
2079
2337
2080
-
as-table@1.0.55:
2081
-
dependencies:
2082
-
printable-characters: 1.0.42
2083
-
2084
-
autoprefixer@10.4.20(postcss@8.5.1):
2338
+
autoprefixer@10.4.22(postcss@8.5.6):
2085
2339
dependencies:
2086
-
browserslist: 4.24.4
2087
-
caniuse-lite: 1.0.30001696
2088
-
fraction.js: 4.3.7
2340
+
browserslist: 4.28.1
2341
+
caniuse-lite: 1.0.30001760
2342
+
fraction.js: 5.3.4
2089
2343
normalize-range: 0.1.2
2090
2344
picocolors: 1.1.1
2091
-
postcss: 8.5.1
2345
+
postcss: 8.5.6
2092
2346
postcss-value-parser: 4.2.0
2093
2347
2094
-
babel-plugin-jsx-dom-expressions@0.39.6(@babel/core@7.26.7):
2348
+
babel-plugin-jsx-dom-expressions@0.40.3(@babel/core@7.28.5):
2095
2349
dependencies:
2096
-
'@babel/core': 7.26.7
2350
+
'@babel/core': 7.28.5
2097
2351
'@babel/helper-module-imports': 7.18.6
2098
-
'@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.7)
2099
-
'@babel/types': 7.26.7
2352
+
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5)
2353
+
'@babel/types': 7.28.5
2100
2354
html-entities: 2.3.3
2101
-
parse5: 7.2.1
2102
-
validate-html-nesting: 1.2.2
2355
+
parse5: 7.3.0
2103
2356
2104
-
babel-preset-solid@1.9.3(@babel/core@7.26.7):
2357
+
babel-preset-solid@1.9.10(@babel/core@7.28.5)(solid-js@1.9.10):
2105
2358
dependencies:
2106
-
'@babel/core': 7.26.7
2107
-
babel-plugin-jsx-dom-expressions: 0.39.6(@babel/core@7.26.7)
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
2108
2363
2109
-
balanced-match@1.0.2: {}
2364
+
baseline-browser-mapping@2.9.5: {}
2110
2365
2111
2366
binary-extensions@2.3.0: {}
2112
2367
2113
2368
blake3-wasm@2.1.5: {}
2114
2369
2115
-
brace-expansion@2.0.1:
2116
-
dependencies:
2117
-
balanced-match: 1.0.2
2118
-
2119
2370
braces@3.0.3:
2120
2371
dependencies:
2121
2372
fill-range: 7.1.1
2122
2373
2123
-
browserslist@4.24.4:
2374
+
browserslist@4.28.1:
2124
2375
dependencies:
2125
-
caniuse-lite: 1.0.30001696
2126
-
electron-to-chromium: 1.5.90
2127
-
node-releases: 2.0.19
2128
-
update-browserslist-db: 1.1.2(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)
2129
2381
2130
2382
buffer-from@1.1.2: {}
2131
2383
2132
2384
camelcase-css@2.0.1: {}
2133
2385
2134
-
caniuse-lite@1.0.30001696: {}
2135
-
2136
-
capnp-ts@0.7.0:
2137
-
dependencies:
2138
-
debug: 4.4.0
2139
-
tslib: 2.8.1
2140
-
transitivePeerDependencies:
2141
-
- supports-color
2386
+
caniuse-lite@1.0.30001760: {}
2142
2387
2143
2388
chokidar@3.6.0:
2144
2389
dependencies:
···
2158
2403
2159
2404
color-name@1.1.4: {}
2160
2405
2406
+
color-string@1.9.1:
2407
+
dependencies:
2408
+
color-name: 1.1.4
2409
+
simple-swizzle: 0.2.4
2410
+
2411
+
color@4.2.3:
2412
+
dependencies:
2413
+
color-convert: 2.0.1
2414
+
color-string: 1.9.1
2415
+
2161
2416
commander@2.20.3: {}
2162
2417
2163
2418
commander@4.1.1: {}
2164
-
2165
-
confbox@0.1.8: {}
2166
2419
2167
2420
convert-source-map@2.0.0: {}
2168
2421
2169
-
cookie@0.7.2: {}
2170
-
2171
-
cross-spawn@7.0.6:
2172
-
dependencies:
2173
-
path-key: 3.1.1
2174
-
shebang-command: 2.0.0
2175
-
which: 2.0.2
2422
+
cookie@1.1.1: {}
2176
2423
2177
2424
cssesc@3.0.0: {}
2178
2425
2179
-
csstype@3.1.3: {}
2426
+
csstype@3.2.3: {}
2180
2427
2181
-
data-uri-to-buffer@2.0.2: {}
2182
-
2183
-
debug@4.4.0:
2428
+
debug@4.4.3:
2184
2429
dependencies:
2185
2430
ms: 2.1.3
2186
2431
2187
-
defu@6.1.4: {}
2432
+
detect-libc@2.1.2: {}
2188
2433
2189
2434
didyoumean@1.2.2: {}
2190
2435
2191
2436
dlv@1.1.3: {}
2192
2437
2193
-
eastasianwidth@0.2.0: {}
2438
+
electron-to-chromium@1.5.267: {}
2194
2439
2195
-
electron-to-chromium@1.5.90: {}
2440
+
entities@6.0.1: {}
2196
2441
2197
-
emoji-regex@8.0.0: {}
2442
+
error-stack-parser-es@1.0.5: {}
2198
2443
2199
-
emoji-regex@9.2.2: {}
2200
-
2201
-
entities@4.5.0: {}
2202
-
2203
-
esbuild@0.17.19:
2444
+
esbuild@0.25.12:
2204
2445
optionalDependencies:
2205
-
'@esbuild/android-arm': 0.17.19
2206
-
'@esbuild/android-arm64': 0.17.19
2207
-
'@esbuild/android-x64': 0.17.19
2208
-
'@esbuild/darwin-arm64': 0.17.19
2209
-
'@esbuild/darwin-x64': 0.17.19
2210
-
'@esbuild/freebsd-arm64': 0.17.19
2211
-
'@esbuild/freebsd-x64': 0.17.19
2212
-
'@esbuild/linux-arm': 0.17.19
2213
-
'@esbuild/linux-arm64': 0.17.19
2214
-
'@esbuild/linux-ia32': 0.17.19
2215
-
'@esbuild/linux-loong64': 0.17.19
2216
-
'@esbuild/linux-mips64el': 0.17.19
2217
-
'@esbuild/linux-ppc64': 0.17.19
2218
-
'@esbuild/linux-riscv64': 0.17.19
2219
-
'@esbuild/linux-s390x': 0.17.19
2220
-
'@esbuild/linux-x64': 0.17.19
2221
-
'@esbuild/netbsd-x64': 0.17.19
2222
-
'@esbuild/openbsd-x64': 0.17.19
2223
-
'@esbuild/sunos-x64': 0.17.19
2224
-
'@esbuild/win32-arm64': 0.17.19
2225
-
'@esbuild/win32-ia32': 0.17.19
2226
-
'@esbuild/win32-x64': 0.17.19
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
2227
2472
2228
-
esbuild@0.24.2:
2473
+
esbuild@0.27.0:
2229
2474
optionalDependencies:
2230
-
'@esbuild/aix-ppc64': 0.24.2
2231
-
'@esbuild/android-arm': 0.24.2
2232
-
'@esbuild/android-arm64': 0.24.2
2233
-
'@esbuild/android-x64': 0.24.2
2234
-
'@esbuild/darwin-arm64': 0.24.2
2235
-
'@esbuild/darwin-x64': 0.24.2
2236
-
'@esbuild/freebsd-arm64': 0.24.2
2237
-
'@esbuild/freebsd-x64': 0.24.2
2238
-
'@esbuild/linux-arm': 0.24.2
2239
-
'@esbuild/linux-arm64': 0.24.2
2240
-
'@esbuild/linux-ia32': 0.24.2
2241
-
'@esbuild/linux-loong64': 0.24.2
2242
-
'@esbuild/linux-mips64el': 0.24.2
2243
-
'@esbuild/linux-ppc64': 0.24.2
2244
-
'@esbuild/linux-riscv64': 0.24.2
2245
-
'@esbuild/linux-s390x': 0.24.2
2246
-
'@esbuild/linux-x64': 0.24.2
2247
-
'@esbuild/netbsd-arm64': 0.24.2
2248
-
'@esbuild/netbsd-x64': 0.24.2
2249
-
'@esbuild/openbsd-arm64': 0.24.2
2250
-
'@esbuild/openbsd-x64': 0.24.2
2251
-
'@esbuild/sunos-x64': 0.24.2
2252
-
'@esbuild/win32-arm64': 0.24.2
2253
-
'@esbuild/win32-ia32': 0.24.2
2254
-
'@esbuild/win32-x64': 0.24.2
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
2255
2501
2256
2502
escalade@3.2.0: {}
2257
2503
2258
-
escape-string-regexp@4.0.0: {}
2259
-
2260
-
estree-walker@0.6.1: {}
2504
+
esm-env@1.2.2: {}
2261
2505
2262
2506
exit-hook@2.2.1: {}
2263
2507
···
2269
2513
merge2: 1.4.1
2270
2514
micromatch: 4.0.8
2271
2515
2272
-
fastq@1.18.0:
2516
+
fastq@1.19.1:
2273
2517
dependencies:
2274
-
reusify: 1.0.4
2518
+
reusify: 1.1.0
2519
+
2520
+
fdir@6.5.0(picomatch@4.0.3):
2521
+
optionalDependencies:
2522
+
picomatch: 4.0.3
2275
2523
2276
2524
fetch-blob@3.2.0:
2277
2525
dependencies:
···
2283
2531
dependencies:
2284
2532
to-regex-range: 5.0.1
2285
2533
2286
-
foreground-child@3.3.0:
2287
-
dependencies:
2288
-
cross-spawn: 7.0.6
2289
-
signal-exit: 4.1.0
2290
-
2291
-
fraction.js@4.3.7: {}
2534
+
fraction.js@5.3.4: {}
2292
2535
2293
2536
fsevents@2.3.3:
2294
2537
optional: true
···
2297
2540
2298
2541
gensync@1.0.0-beta.2: {}
2299
2542
2300
-
get-source@2.0.12:
2301
-
dependencies:
2302
-
data-uri-to-buffer: 2.0.2
2303
-
source-map: 0.6.1
2304
-
2305
2543
glob-parent@5.1.2:
2306
2544
dependencies:
2307
2545
is-glob: 4.0.3
···
2312
2550
2313
2551
glob-to-regexp@0.4.1: {}
2314
2552
2315
-
glob@10.4.5:
2316
-
dependencies:
2317
-
foreground-child: 3.3.0
2318
-
jackspeak: 3.4.3
2319
-
minimatch: 9.0.5
2320
-
minipass: 7.1.2
2321
-
package-json-from-dist: 1.0.1
2322
-
path-scurry: 1.11.1
2323
-
2324
-
globals@11.12.0: {}
2325
-
2326
2553
hasown@2.0.2:
2327
2554
dependencies:
2328
2555
function-bind: 1.1.2
2329
2556
2330
2557
html-entities@2.3.3: {}
2331
2558
2559
+
is-arrayish@0.3.4: {}
2560
+
2332
2561
is-binary-path@2.1.0:
2333
2562
dependencies:
2334
2563
binary-extensions: 2.3.0
···
2338
2567
hasown: 2.0.2
2339
2568
2340
2569
is-extglob@2.1.1: {}
2341
-
2342
-
is-fullwidth-code-point@3.0.0: {}
2343
2570
2344
2571
is-glob@4.0.3:
2345
2572
dependencies:
···
2349
2576
2350
2577
is-what@4.1.16: {}
2351
2578
2352
-
isexe@2.0.0: {}
2353
-
2354
-
jackspeak@3.4.3:
2355
-
dependencies:
2356
-
'@isaacs/cliui': 8.0.2
2357
-
optionalDependencies:
2358
-
'@pkgjs/parseargs': 0.11.0
2359
-
2360
2579
jiti@1.21.7: {}
2361
2580
2362
2581
js-tokens@4.0.0: {}
···
2365
2584
2366
2585
json5@2.2.3: {}
2367
2586
2587
+
kleur@4.1.5: {}
2588
+
2368
2589
lilconfig@3.1.3: {}
2369
2590
2370
2591
lines-and-columns@1.2.4: {}
2371
-
2372
-
lru-cache@10.4.3: {}
2373
2592
2374
2593
lru-cache@5.1.1:
2375
2594
dependencies:
2376
2595
yallist: 3.1.1
2377
2596
2378
-
magic-string@0.25.9:
2379
-
dependencies:
2380
-
sourcemap-codec: 1.4.8
2381
-
2382
2597
merge-anything@5.1.7:
2383
2598
dependencies:
2384
2599
is-what: 4.1.16
···
2394
2609
2395
2610
mini-svg-data-uri@1.4.4: {}
2396
2611
2397
-
miniflare@3.20250124.0:
2612
+
miniflare@4.20251202.1:
2398
2613
dependencies:
2399
2614
'@cspotcode/source-map-support': 0.8.1
2400
2615
acorn: 8.14.0
2401
-
acorn-walk: 8.3.4
2402
-
capnp-ts: 0.7.0
2616
+
acorn-walk: 8.3.2
2403
2617
exit-hook: 2.2.1
2404
2618
glob-to-regexp: 0.4.1
2619
+
sharp: 0.33.5
2405
2620
stoppable: 1.1.0
2406
-
undici: 5.28.5
2407
-
workerd: 1.20250124.0
2621
+
undici: 7.14.0
2622
+
workerd: 1.20251202.0
2408
2623
ws: 8.18.0
2409
-
youch: 3.3.4
2410
-
zod: 3.24.1
2624
+
youch: 4.1.0-beta.10
2625
+
zod: 3.22.3
2411
2626
transitivePeerDependencies:
2412
2627
- bufferutil
2413
-
- supports-color
2414
2628
- utf-8-validate
2415
2629
2416
-
minimatch@9.0.5:
2417
-
dependencies:
2418
-
brace-expansion: 2.0.1
2419
-
2420
-
minipass@7.1.2: {}
2421
-
2422
-
mlly@1.7.4:
2423
-
dependencies:
2424
-
acorn: 8.14.0
2425
-
pathe: 2.0.2
2426
-
pkg-types: 1.3.1
2427
-
ufo: 1.5.4
2428
-
2429
2630
ms@2.1.3: {}
2430
-
2431
-
mustache@4.2.0: {}
2432
2631
2433
2632
mz@2.7.0:
2434
2633
dependencies:
···
2436
2635
object-assign: 4.1.1
2437
2636
thenify-all: 1.6.0
2438
2637
2439
-
nanoid@3.3.8: {}
2638
+
nanoid@3.3.11: {}
2440
2639
2441
-
nanoid@5.0.9: {}
2640
+
nanoid@5.1.6: {}
2442
2641
2443
2642
native-file-system-adapter@3.0.1:
2444
2643
optionalDependencies:
···
2447
2646
node-domexception@1.0.0:
2448
2647
optional: true
2449
2648
2450
-
node-releases@2.0.19: {}
2649
+
node-releases@2.0.27: {}
2451
2650
2452
2651
normalize-path@3.0.0: {}
2453
2652
···
2457
2656
2458
2657
object-hash@3.0.0: {}
2459
2658
2460
-
ohash@1.1.4: {}
2461
-
2462
-
package-json-from-dist@1.0.1: {}
2463
-
2464
-
parse5@7.2.1:
2659
+
parse5@7.3.0:
2465
2660
dependencies:
2466
-
entities: 4.5.0
2467
-
2468
-
path-key@3.1.1: {}
2661
+
entities: 6.0.1
2469
2662
2470
2663
path-parse@1.0.7: {}
2471
2664
2472
-
path-scurry@1.11.1:
2473
-
dependencies:
2474
-
lru-cache: 10.4.3
2475
-
minipass: 7.1.2
2476
-
2477
2665
path-to-regexp@6.3.0: {}
2478
2666
2479
-
pathe@1.1.2: {}
2480
-
2481
-
pathe@2.0.2: {}
2667
+
pathe@2.0.3: {}
2482
2668
2483
2669
picocolors@1.1.1: {}
2484
2670
2485
2671
picomatch@2.3.1: {}
2486
2672
2487
-
pify@2.3.0: {}
2673
+
picomatch@4.0.3: {}
2488
2674
2489
-
pirates@4.0.6: {}
2675
+
pify@2.3.0: {}
2490
2676
2491
-
pkg-types@1.3.1:
2492
-
dependencies:
2493
-
confbox: 0.1.8
2494
-
mlly: 1.7.4
2495
-
pathe: 2.0.2
2677
+
pirates@4.0.7: {}
2496
2678
2497
-
postcss-import@15.1.0(postcss@8.5.1):
2679
+
postcss-import@15.1.0(postcss@8.5.6):
2498
2680
dependencies:
2499
-
postcss: 8.5.1
2681
+
postcss: 8.5.6
2500
2682
postcss-value-parser: 4.2.0
2501
2683
read-cache: 1.0.0
2502
-
resolve: 1.22.10
2684
+
resolve: 1.22.11
2503
2685
2504
-
postcss-js@4.0.1(postcss@8.5.1):
2686
+
postcss-js@4.1.0(postcss@8.5.6):
2505
2687
dependencies:
2506
2688
camelcase-css: 2.0.1
2507
-
postcss: 8.5.1
2689
+
postcss: 8.5.6
2508
2690
2509
-
postcss-load-config@4.0.2(postcss@8.5.1):
2691
+
postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6):
2510
2692
dependencies:
2511
2693
lilconfig: 3.1.3
2512
-
yaml: 2.7.0
2513
2694
optionalDependencies:
2514
-
postcss: 8.5.1
2695
+
jiti: 1.21.7
2696
+
postcss: 8.5.6
2515
2697
2516
-
postcss-nested@6.2.0(postcss@8.5.1):
2698
+
postcss-nested@6.2.0(postcss@8.5.6):
2517
2699
dependencies:
2518
-
postcss: 8.5.1
2700
+
postcss: 8.5.6
2519
2701
postcss-selector-parser: 6.1.2
2520
2702
2521
2703
postcss-selector-parser@6.1.2:
···
2525
2707
2526
2708
postcss-value-parser@4.2.0: {}
2527
2709
2528
-
postcss@8.5.1:
2710
+
postcss@8.5.6:
2529
2711
dependencies:
2530
-
nanoid: 3.3.8
2712
+
nanoid: 3.3.11
2531
2713
picocolors: 1.1.1
2532
2714
source-map-js: 1.2.1
2533
2715
2534
-
prettier-plugin-tailwindcss@0.6.11(prettier@3.4.2):
2716
+
prettier-plugin-tailwindcss@0.6.14(prettier@3.7.4):
2535
2717
dependencies:
2536
-
prettier: 3.4.2
2718
+
prettier: 3.7.4
2537
2719
2538
-
prettier@3.4.2: {}
2539
-
2540
-
printable-characters@1.0.42: {}
2720
+
prettier@3.7.4: {}
2541
2721
2542
2722
queue-microtask@1.2.3: {}
2543
2723
···
2549
2729
dependencies:
2550
2730
picomatch: 2.3.1
2551
2731
2552
-
resolve@1.22.10:
2732
+
resolve@1.22.11:
2553
2733
dependencies:
2554
2734
is-core-module: 2.16.1
2555
2735
path-parse: 1.0.7
2556
2736
supports-preserve-symlinks-flag: 1.0.0
2557
2737
2558
-
reusify@1.0.4: {}
2738
+
reusify@1.1.0: {}
2559
2739
2560
-
rollup-plugin-inject@3.0.2:
2740
+
rollup@4.53.3:
2561
2741
dependencies:
2562
-
estree-walker: 0.6.1
2563
-
magic-string: 0.25.9
2564
-
rollup-pluginutils: 2.8.2
2565
-
2566
-
rollup-plugin-node-polyfills@0.2.1:
2567
-
dependencies:
2568
-
rollup-plugin-inject: 3.0.2
2569
-
2570
-
rollup-pluginutils@2.8.2:
2571
-
dependencies:
2572
-
estree-walker: 0.6.1
2573
-
2574
-
rollup@4.32.1:
2575
-
dependencies:
2576
-
'@types/estree': 1.0.6
2742
+
'@types/estree': 1.0.8
2577
2743
optionalDependencies:
2578
-
'@rollup/rollup-android-arm-eabi': 4.32.1
2579
-
'@rollup/rollup-android-arm64': 4.32.1
2580
-
'@rollup/rollup-darwin-arm64': 4.32.1
2581
-
'@rollup/rollup-darwin-x64': 4.32.1
2582
-
'@rollup/rollup-freebsd-arm64': 4.32.1
2583
-
'@rollup/rollup-freebsd-x64': 4.32.1
2584
-
'@rollup/rollup-linux-arm-gnueabihf': 4.32.1
2585
-
'@rollup/rollup-linux-arm-musleabihf': 4.32.1
2586
-
'@rollup/rollup-linux-arm64-gnu': 4.32.1
2587
-
'@rollup/rollup-linux-arm64-musl': 4.32.1
2588
-
'@rollup/rollup-linux-loongarch64-gnu': 4.32.1
2589
-
'@rollup/rollup-linux-powerpc64le-gnu': 4.32.1
2590
-
'@rollup/rollup-linux-riscv64-gnu': 4.32.1
2591
-
'@rollup/rollup-linux-s390x-gnu': 4.32.1
2592
-
'@rollup/rollup-linux-x64-gnu': 4.32.1
2593
-
'@rollup/rollup-linux-x64-musl': 4.32.1
2594
-
'@rollup/rollup-win32-arm64-msvc': 4.32.1
2595
-
'@rollup/rollup-win32-ia32-msvc': 4.32.1
2596
-
'@rollup/rollup-win32-x64-msvc': 4.32.1
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
2597
2766
fsevents: 2.3.3
2598
2767
2599
2768
run-parallel@1.2.0:
···
2602
2771
2603
2772
semver@6.3.1: {}
2604
2773
2605
-
seroval-plugins@1.2.0(seroval@1.2.0):
2774
+
semver@7.7.3: {}
2775
+
2776
+
seroval-plugins@1.3.3(seroval@1.3.2):
2606
2777
dependencies:
2607
-
seroval: 1.2.0
2778
+
seroval: 1.3.2
2608
2779
2609
-
seroval@1.2.0: {}
2780
+
seroval@1.3.2: {}
2610
2781
2611
-
shebang-command@2.0.0:
2782
+
sharp@0.33.5:
2612
2783
dependencies:
2613
-
shebang-regex: 3.0.0
2614
-
2615
-
shebang-regex@3.0.0: {}
2784
+
color: 4.2.3
2785
+
detect-libc: 2.1.2
2786
+
semver: 7.7.3
2787
+
optionalDependencies:
2788
+
'@img/sharp-darwin-arm64': 0.33.5
2789
+
'@img/sharp-darwin-x64': 0.33.5
2790
+
'@img/sharp-libvips-darwin-arm64': 1.0.4
2791
+
'@img/sharp-libvips-darwin-x64': 1.0.4
2792
+
'@img/sharp-libvips-linux-arm': 1.0.5
2793
+
'@img/sharp-libvips-linux-arm64': 1.0.4
2794
+
'@img/sharp-libvips-linux-s390x': 1.0.4
2795
+
'@img/sharp-libvips-linux-x64': 1.0.4
2796
+
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
2797
+
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
2798
+
'@img/sharp-linux-arm': 0.33.5
2799
+
'@img/sharp-linux-arm64': 0.33.5
2800
+
'@img/sharp-linux-s390x': 0.33.5
2801
+
'@img/sharp-linux-x64': 0.33.5
2802
+
'@img/sharp-linuxmusl-arm64': 0.33.5
2803
+
'@img/sharp-linuxmusl-x64': 0.33.5
2804
+
'@img/sharp-wasm32': 0.33.5
2805
+
'@img/sharp-win32-ia32': 0.33.5
2806
+
'@img/sharp-win32-x64': 0.33.5
2616
2807
2617
-
signal-exit@4.1.0: {}
2808
+
simple-swizzle@0.2.4:
2809
+
dependencies:
2810
+
is-arrayish: 0.3.4
2618
2811
2619
-
solid-js@1.9.4:
2812
+
solid-js@1.9.10:
2620
2813
dependencies:
2621
-
csstype: 3.1.3
2622
-
seroval: 1.2.0
2623
-
seroval-plugins: 1.2.0(seroval@1.2.0)
2814
+
csstype: 3.2.3
2815
+
seroval: 1.3.2
2816
+
seroval-plugins: 1.3.3(seroval@1.3.2)
2624
2817
2625
-
solid-refresh@0.6.3(solid-js@1.9.4):
2818
+
solid-refresh@0.6.3(solid-js@1.9.10):
2626
2819
dependencies:
2627
-
'@babel/generator': 7.26.5
2628
-
'@babel/helper-module-imports': 7.25.9
2629
-
'@babel/types': 7.26.7
2630
-
solid-js: 1.9.4
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
2631
2824
transitivePeerDependencies:
2632
2825
- supports-color
2633
2826
···
2639
2832
source-map: 0.6.1
2640
2833
2641
2834
source-map@0.6.1: {}
2642
-
2643
-
sourcemap-codec@1.4.8: {}
2644
-
2645
-
stacktracey@2.1.8:
2646
-
dependencies:
2647
-
as-table: 1.0.55
2648
-
get-source: 2.0.12
2649
2835
2650
2836
stoppable@1.1.0: {}
2651
2837
2652
-
string-width@4.2.3:
2653
-
dependencies:
2654
-
emoji-regex: 8.0.0
2655
-
is-fullwidth-code-point: 3.0.0
2656
-
strip-ansi: 6.0.1
2657
-
2658
-
string-width@5.1.2:
2659
-
dependencies:
2660
-
eastasianwidth: 0.2.0
2661
-
emoji-regex: 9.2.2
2662
-
strip-ansi: 7.1.0
2663
-
2664
-
strip-ansi@6.0.1:
2665
-
dependencies:
2666
-
ansi-regex: 5.0.1
2667
-
2668
-
strip-ansi@7.1.0:
2838
+
sucrase@3.35.1:
2669
2839
dependencies:
2670
-
ansi-regex: 6.1.0
2671
-
2672
-
sucrase@3.35.0:
2673
-
dependencies:
2674
-
'@jridgewell/gen-mapping': 0.3.8
2840
+
'@jridgewell/gen-mapping': 0.3.13
2675
2841
commander: 4.1.1
2676
-
glob: 10.4.5
2677
2842
lines-and-columns: 1.2.4
2678
2843
mz: 2.7.0
2679
-
pirates: 4.0.6
2844
+
pirates: 4.0.7
2845
+
tinyglobby: 0.2.15
2680
2846
ts-interface-checker: 0.1.13
2681
2847
2848
+
supports-color@10.2.2: {}
2849
+
2682
2850
supports-preserve-symlinks-flag@1.0.0: {}
2683
2851
2684
-
tailwindcss@3.4.17:
2852
+
tailwindcss@3.4.18:
2685
2853
dependencies:
2686
2854
'@alloc/quick-lru': 5.2.0
2687
2855
arg: 5.0.2
···
2697
2865
normalize-path: 3.0.0
2698
2866
object-hash: 3.0.0
2699
2867
picocolors: 1.1.1
2700
-
postcss: 8.5.1
2701
-
postcss-import: 15.1.0(postcss@8.5.1)
2702
-
postcss-js: 4.0.1(postcss@8.5.1)
2703
-
postcss-load-config: 4.0.2(postcss@8.5.1)
2704
-
postcss-nested: 6.2.0(postcss@8.5.1)
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)
2705
2873
postcss-selector-parser: 6.1.2
2706
-
resolve: 1.22.10
2707
-
sucrase: 3.35.0
2874
+
resolve: 1.22.11
2875
+
sucrase: 3.35.1
2708
2876
transitivePeerDependencies:
2709
-
- ts-node
2877
+
- tsx
2878
+
- yaml
2710
2879
2711
-
terser@5.37.0:
2880
+
terser@5.44.1:
2712
2881
dependencies:
2713
-
'@jridgewell/source-map': 0.3.6
2714
-
acorn: 8.14.0
2882
+
'@jridgewell/source-map': 0.3.11
2883
+
acorn: 8.15.0
2715
2884
commander: 2.20.3
2716
2885
source-map-support: 0.5.21
2717
2886
···
2723
2892
dependencies:
2724
2893
any-promise: 1.3.0
2725
2894
2895
+
tinyglobby@0.2.15:
2896
+
dependencies:
2897
+
fdir: 6.5.0(picomatch@4.0.3)
2898
+
picomatch: 4.0.3
2899
+
2726
2900
to-regex-range@5.0.1:
2727
2901
dependencies:
2728
2902
is-number: 7.0.0
2729
2903
2730
2904
ts-interface-checker@0.1.13: {}
2731
2905
2732
-
tslib@2.8.1: {}
2906
+
tslib@2.8.1:
2907
+
optional: true
2733
2908
2734
-
typescript@5.7.2: {}
2735
-
2736
-
ufo@1.5.4: {}
2909
+
typescript@5.9.3: {}
2737
2910
2738
-
undici-types@6.20.0: {}
2911
+
undici-types@6.21.0: {}
2739
2912
2740
-
undici@5.28.5:
2741
-
dependencies:
2742
-
'@fastify/busboy': 2.1.1
2913
+
undici@7.14.0: {}
2743
2914
2744
-
unenv@2.0.0-rc.1:
2915
+
unenv@2.0.0-rc.24:
2745
2916
dependencies:
2746
-
defu: 6.1.4
2747
-
mlly: 1.7.4
2748
-
ohash: 1.1.4
2749
-
pathe: 1.1.2
2750
-
ufo: 1.5.4
2917
+
pathe: 2.0.3
2751
2918
2752
-
update-browserslist-db@1.1.2(browserslist@4.24.4):
2919
+
update-browserslist-db@1.2.2(browserslist@4.28.1):
2753
2920
dependencies:
2754
-
browserslist: 4.24.4
2921
+
browserslist: 4.28.1
2755
2922
escalade: 3.2.0
2756
2923
picocolors: 1.1.1
2757
2924
2758
2925
util-deprecate@1.0.2: {}
2759
2926
2760
-
validate-html-nesting@1.2.2: {}
2761
-
2762
-
vite-plugin-solid@2.11.0(solid-js@1.9.4)(vite@6.0.11(@types/node@22.12.0)(jiti@1.21.7)(terser@5.37.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)):
2763
2928
dependencies:
2764
-
'@babel/core': 7.26.7
2929
+
'@babel/core': 7.28.5
2765
2930
'@types/babel__core': 7.20.5
2766
-
babel-preset-solid: 1.9.3(@babel/core@7.26.7)
2931
+
babel-preset-solid: 1.9.10(@babel/core@7.28.5)(solid-js@1.9.10)
2767
2932
merge-anything: 5.1.7
2768
-
solid-js: 1.9.4
2769
-
solid-refresh: 0.6.3(solid-js@1.9.4)
2770
-
vite: 6.0.11(@types/node@22.12.0)(jiti@1.21.7)(terser@5.37.0)(yaml@2.7.0)
2771
-
vitefu: 1.0.5(vite@6.0.11(@types/node@22.12.0)(jiti@1.21.7)(terser@5.37.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))
2772
2937
transitivePeerDependencies:
2773
2938
- supports-color
2774
2939
2775
-
vite@6.0.11(@types/node@22.12.0)(jiti@1.21.7)(terser@5.37.0)(yaml@2.7.0):
2940
+
vite@7.2.7(@types/node@22.19.2)(jiti@1.21.7)(terser@5.44.1):
2776
2941
dependencies:
2777
-
esbuild: 0.24.2
2778
-
postcss: 8.5.1
2779
-
rollup: 4.32.1
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
2780
2948
optionalDependencies:
2781
-
'@types/node': 22.12.0
2949
+
'@types/node': 22.19.2
2782
2950
fsevents: 2.3.3
2783
2951
jiti: 1.21.7
2784
-
terser: 5.37.0
2785
-
yaml: 2.7.0
2952
+
terser: 5.44.1
2786
2953
2787
-
vitefu@1.0.5(vite@6.0.11(@types/node@22.12.0)(jiti@1.21.7)(terser@5.37.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)):
2788
2955
optionalDependencies:
2789
-
vite: 6.0.11(@types/node@22.12.0)(jiti@1.21.7)(terser@5.37.0)(yaml@2.7.0)
2956
+
vite: 7.2.7(@types/node@22.19.2)(jiti@1.21.7)(terser@5.44.1)
2790
2957
2791
2958
web-streams-polyfill@3.3.3:
2792
2959
optional: true
2793
2960
2794
-
which@2.0.2:
2795
-
dependencies:
2796
-
isexe: 2.0.0
2797
-
2798
-
workerd@1.20250124.0:
2961
+
workerd@1.20251202.0:
2799
2962
optionalDependencies:
2800
-
'@cloudflare/workerd-darwin-64': 1.20250124.0
2801
-
'@cloudflare/workerd-darwin-arm64': 1.20250124.0
2802
-
'@cloudflare/workerd-linux-64': 1.20250124.0
2803
-
'@cloudflare/workerd-linux-arm64': 1.20250124.0
2804
-
'@cloudflare/workerd-windows-64': 1.20250124.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
2805
2968
2806
-
wrangler@3.106.0:
2969
+
wrangler@4.53.0:
2807
2970
dependencies:
2808
-
'@cloudflare/kv-asset-handler': 0.3.4
2809
-
'@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19)
2810
-
'@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19)
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)
2811
2973
blake3-wasm: 2.1.5
2812
-
esbuild: 0.17.19
2813
-
miniflare: 3.20250124.0
2974
+
esbuild: 0.27.0
2975
+
miniflare: 4.20251202.1
2814
2976
path-to-regexp: 6.3.0
2815
-
unenv: 2.0.0-rc.1
2816
-
workerd: 1.20250124.0
2977
+
unenv: 2.0.0-rc.24
2978
+
workerd: 1.20251202.0
2817
2979
optionalDependencies:
2818
2980
fsevents: 2.3.3
2819
2981
transitivePeerDependencies:
2820
2982
- bufferutil
2821
-
- supports-color
2822
2983
- utf-8-validate
2823
2984
2824
-
wrap-ansi@7.0.0:
2825
-
dependencies:
2826
-
ansi-styles: 4.3.0
2827
-
string-width: 4.2.3
2828
-
strip-ansi: 6.0.1
2829
-
2830
-
wrap-ansi@8.1.0:
2831
-
dependencies:
2832
-
ansi-styles: 6.2.1
2833
-
string-width: 5.1.2
2834
-
strip-ansi: 7.1.0
2835
-
2836
2985
ws@8.18.0: {}
2837
2986
2838
2987
yallist@3.1.1: {}
2839
2988
2840
-
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
2841
2993
2842
-
youch@3.3.4:
2994
+
youch@4.1.0-beta.10:
2843
2995
dependencies:
2844
-
cookie: 0.7.2
2845
-
mustache: 4.2.0
2846
-
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
2847
3001
2848
-
zod@3.24.1: {}
3002
+
zod@3.22.3: {}
+15
-47
src/api/queries/did-doc.ts
+15
-47
src/api/queries/did-doc.ts
···
1
-
import { At } from '@atcute/client/lexicons';
1
+
import type { DidDocument } from '@atcute/identity';
2
+
import {
3
+
CompositeDidDocumentResolver,
4
+
PlcDidDocumentResolver,
5
+
WebDidDocumentResolver,
6
+
} from '@atcute/identity-resolver';
7
+
import type { AtprotoDid } from '@atcute/lexicons/syntax';
2
8
3
-
import { didDocument, DidDocument } from '../types/did-doc';
4
-
import { DID_PLC_RE, DID_WEB_RE } from '../utils/strings';
9
+
const didDocumentResolver = new CompositeDidDocumentResolver({
10
+
methods: {
11
+
plc: new PlcDidDocumentResolver(),
12
+
web: new WebDidDocumentResolver(),
13
+
},
14
+
});
5
15
6
16
export const getDidDocument = async ({
7
17
did,
8
18
signal,
9
19
}: {
10
-
did: At.DID;
20
+
did: AtprotoDid;
11
21
signal?: AbortSignal;
12
22
}): Promise<DidDocument> => {
13
-
const colon_index = did.indexOf(':', 4);
14
-
15
-
const type = did.slice(4, colon_index);
16
-
const ident = did.slice(colon_index + 1);
17
-
18
-
let rawDoc: any;
19
-
20
-
if (type === 'plc') {
21
-
if (!DID_PLC_RE.test(did)) {
22
-
throw new Error(`invalid did:plc identifier`);
23
-
}
24
-
25
-
const origin = import.meta.env.VITE_PLC_DIRECTORY_URL;
26
-
const response = await fetch(`${origin}/${did}`, { signal });
27
-
28
-
if (response.status === 404) {
29
-
throw new Error(`did not found in directory`);
30
-
} else if (!response.ok) {
31
-
throw new Error(`directory is unreachable`);
32
-
}
33
-
34
-
const json = await response.json();
35
-
36
-
rawDoc = json;
37
-
} else if (type === 'web') {
38
-
if (!DID_WEB_RE.test(did)) {
39
-
throw new Error(`invalid did:web identifier`);
40
-
}
41
-
42
-
const response = await fetch(`https://${ident}/.well-known/did.json`, { signal });
43
-
44
-
if (!response.ok) {
45
-
throw new Error(`did document is unreachable`);
46
-
}
47
-
48
-
const json = await response.json();
49
-
50
-
rawDoc = json;
51
-
} else {
52
-
throw new Error(`unsupported did method`);
53
-
}
54
-
55
-
return didDocument.parse(rawDoc, { mode: 'passthrough' });
23
+
return didDocumentResolver.resolve(did, { signal });
56
24
};
+15
-19
src/api/queries/handle.ts
+15
-19
src/api/queries/handle.ts
···
1
-
import { simpleFetchHandler, XRPC } from '@atcute/client';
2
-
import { At } from '@atcute/client/lexicons';
1
+
import { XrpcHandleResolver } from '@atcute/identity-resolver';
2
+
import { type AtprotoDid, type Handle, isHandle } from '@atcute/lexicons/syntax';
3
3
4
-
import { appViewRpc } from '~/globals/rpc';
4
+
const handleResolver = new XrpcHandleResolver({
5
+
serviceUrl: import.meta.env.VITE_APPVIEW_URL,
6
+
});
5
7
6
8
export const resolveHandleViaAppView = async ({
7
9
handle,
8
10
signal,
9
11
}: {
10
-
handle: string;
12
+
handle: Handle;
11
13
signal?: AbortSignal;
12
-
}): Promise<At.DID> => {
13
-
const { data } = await appViewRpc.get('com.atproto.identity.resolveHandle', {
14
-
signal: signal,
15
-
params: { handle: handle },
16
-
});
14
+
}): Promise<AtprotoDid> => {
15
+
if (!isHandle(handle)) {
16
+
throw new Error(`invalid handle: ${handle}`);
17
+
}
17
18
18
-
return data.did;
19
+
return await handleResolver.resolve(handle, { signal });
19
20
};
20
21
21
22
export const resolveHandleViaPds = async ({
···
24
25
signal,
25
26
}: {
26
27
service: string;
27
-
handle: string;
28
+
handle: Handle;
28
29
signal?: AbortSignal;
29
-
}): Promise<At.DID> => {
30
-
const rpc = new XRPC({ handler: simpleFetchHandler({ service }) });
30
+
}): Promise<AtprotoDid> => {
31
+
const resolver = new XrpcHandleResolver({ serviceUrl: service });
31
32
32
-
const { data } = await rpc.get('com.atproto.identity.resolveHandle', {
33
-
signal,
34
-
params: { handle },
35
-
});
36
-
37
-
return data.did;
33
+
return await resolver.resolve(handle, { signal });
38
34
};
+4
-5
src/api/queries/plc.ts
+4
-5
src/api/queries/plc.ts
···
1
-
import { At } from '@atcute/client/lexicons';
1
+
import { defs } from '@atcute/did-plc';
2
+
import type { Did } from '@atcute/lexicons/syntax';
2
3
3
-
import { plcLogEntries } from '../types/plc';
4
-
5
-
export const getPlcAuditLogs = async ({ did, signal }: { did: At.DID; signal?: AbortSignal }) => {
4
+
export const getPlcAuditLogs = async ({ did, signal }: { did: Did<'plc'>; signal?: AbortSignal }) => {
6
5
const origin = import.meta.env.VITE_PLC_DIRECTORY_URL;
7
6
const response = await fetch(`${origin}/${did}/log/audit`, { signal });
8
7
if (!response.ok) {
···
10
9
}
11
10
12
11
const json = await response.json();
13
-
return plcLogEntries.parse(json);
12
+
return defs.indexedEntryLog.parse(json);
14
13
};
-86
src/api/types/did-doc.ts
-86
src/api/types/did-doc.ts
···
1
-
import * as v from '@badrap/valita';
2
-
3
-
import { didString, serviceUrlString, urlString } from './strings';
4
-
5
-
const PUBLIC_KEY_MULTIBASE_RE = /^z[a-km-zA-HJ-NP-Z1-9]+$/;
6
-
7
-
const verificationMethod = v.object({
8
-
id: v.string(),
9
-
type: v.string(),
10
-
controller: didString,
11
-
publicKeyMultibase: v
12
-
.string()
13
-
.assert((input) => PUBLIC_KEY_MULTIBASE_RE.test(input), `must be a valid base58btc multibase key`),
14
-
});
15
-
16
-
const service = v
17
-
.object({
18
-
id: v.string(),
19
-
type: v.string(),
20
-
serviceEndpoint: v.union(urlString, v.record(urlString), v.array(urlString)),
21
-
})
22
-
.chain((input) => {
23
-
switch (input.type) {
24
-
case 'AtprotoPersonalDataServer':
25
-
case 'AtprotoLabeler':
26
-
case 'BskyFeedGenerator':
27
-
case 'BskyNotificationService': {
28
-
const result = serviceUrlString.try(input.serviceEndpoint);
29
-
if (!result.ok) {
30
-
return v.err({
31
-
message: `must be a valid atproto service url`,
32
-
path: ['serviceEndpoint'],
33
-
});
34
-
}
35
-
}
36
-
}
37
-
38
-
return v.ok(input);
39
-
});
40
-
41
-
export const didDocument = v.object({
42
-
'@context': v.array(urlString),
43
-
id: didString,
44
-
alsoKnownAs: v.array(urlString).optional(() => []),
45
-
verificationMethod: v.array(verificationMethod).optional(() => []),
46
-
service: v.array(service).chain((input) => {
47
-
for (let i = 0, len = input.length; i < len; i++) {
48
-
const service = input[i];
49
-
const id = service.id;
50
-
51
-
for (let j = 0; j < i; j++) {
52
-
if (input[j].id === id) {
53
-
return v.err({
54
-
message: `duplicate service id`,
55
-
path: [i, 'id'],
56
-
});
57
-
}
58
-
}
59
-
}
60
-
61
-
return v.ok(input);
62
-
}),
63
-
});
64
-
65
-
export type DidDocument = v.Infer<typeof didDocument>;
66
-
67
-
export const getPdsEndpoint = (doc: DidDocument): string | undefined => {
68
-
return getServiceEndpoint(doc, '#atproto_pds', 'AtprotoPersonalDataServer');
69
-
};
70
-
71
-
export const getServiceEndpoint = (
72
-
doc: DidDocument,
73
-
serviceId: string,
74
-
serviceType: string,
75
-
): string | undefined => {
76
-
const did = doc.id;
77
-
78
-
const didServiceId = did + serviceId;
79
-
const found = doc.service?.find((service) => service.id === serviceId || service.id === didServiceId);
80
-
81
-
if (!found || found.type !== serviceType || typeof found.serviceEndpoint !== 'string') {
82
-
return undefined;
83
-
}
84
-
85
-
return found.serviceEndpoint;
86
-
};
+26
-78
src/api/types/plc.ts
+26
-78
src/api/types/plc.ts
···
1
1
import * as v from '@badrap/valita';
2
2
3
-
import { didKeyString, didString, handleString, serviceUrlString, urlString } from './strings';
4
-
5
-
const legacyGenesisOp = v.object({
6
-
type: v.literal('create'),
7
-
signingKey: didKeyString,
8
-
recoveryKey: didKeyString,
9
-
handle: handleString,
10
-
service: serviceUrlString,
11
-
prev: v.null(),
12
-
sig: v.string(),
13
-
});
14
-
15
-
const tombstoneOp = v.object({
16
-
type: v.literal('plc_tombstone'),
17
-
prev: v.string(),
18
-
sig: v.string(),
19
-
});
20
-
21
-
const service = v.object({
22
-
type: v.string().assert((input) => input.length <= 256, `service type too long (max 256)`),
23
-
endpoint: urlString.assert((input) => input.length <= 512, `service endpoint too long (max 512)`),
24
-
});
25
-
export type Service = v.Infer<typeof service>;
26
-
27
-
const updateOp = v.object({
28
-
type: v.literal('plc_operation'),
29
-
prev: v.string().nullable(),
30
-
sig: v.string(),
31
-
rotationKeys: v.array(didKeyString).chain((input) => {
32
-
const len = input.length;
33
-
34
-
if (len === 0) {
35
-
return v.err({ message: `missing rotation keys` });
36
-
} else if (len > 10) {
37
-
return v.err({ message: `too many rotation keys (max 10)` });
38
-
}
39
-
40
-
for (let i = 0; i < len; i++) {
41
-
const key = input[i];
42
-
43
-
for (let j = 0; j < i; j++) {
44
-
if (input[j] === key) {
45
-
return v.err({
46
-
message: `duplicate rotation key`,
47
-
path: [i],
48
-
});
49
-
}
50
-
}
51
-
}
52
-
53
-
return v.ok(input);
54
-
}),
55
-
verificationMethods: v.record(didKeyString),
56
-
alsoKnownAs: v
57
-
.array(urlString.assert((input) => input.length <= 256, `alsoKnownAs entry too long (max 256)`))
58
-
.assert((input) => input.length <= 10, `too many alsoKnownAs entries (max 10)`),
59
-
services: v
60
-
.record(service)
61
-
.assert((input) => Object.keys(input).length <= 10, `too many service entries (max 10)`),
62
-
});
63
-
export type PlcUpdateOp = v.Infer<typeof updateOp>;
3
+
import { defs, type UnsignedOperation } from '@atcute/did-plc';
64
4
65
-
const plcOperation = v.union(legacyGenesisOp, tombstoneOp, updateOp);
66
-
67
-
export const plcLogEntry = v.object({
68
-
did: didString,
69
-
cid: v.string(),
70
-
operation: plcOperation,
71
-
nullified: v.boolean(),
72
-
createdAt: v
73
-
.string()
74
-
.assert((input) => !Number.isNaN(new Date(input).getTime()), `must be a valid datetime string`),
75
-
});
76
-
export type PlcLogEntry = v.Infer<typeof plcLogEntry>;
5
+
import type { ToValidator } from '../utils/valita';
6
+
import { serviceUrlString } from './strings';
77
7
78
-
export const plcLogEntries = v.array(plcLogEntry);
8
+
const _unsignedOperation = defs.unsignedOperation as ToValidator<UnsignedOperation>;
79
9
80
-
export const updatePayload = updateOp.omit('type', 'prev', 'sig').extend({
10
+
export const updatePayload = _unsignedOperation.omit('type', 'prev').extend({
81
11
services: v
82
12
.record(
83
-
service.chain((input) => {
13
+
defs.service.chain((input) => {
84
14
switch (input.type) {
85
15
case 'AtprotoPersonalDataServer':
86
16
case 'AtprotoLabeler':
···
107
37
return v.ok(input);
108
38
}),
109
39
)
110
-
.assert((input) => Object.keys(input).length <= 10, `too many service entries (max 10)`),
40
+
.chain((input) => {
41
+
const length = Object.keys(input).length;
42
+
43
+
if (length > 10) {
44
+
return v.err(`too many service entries (max 10)`);
45
+
}
46
+
47
+
for (const id in input) {
48
+
if (id.length > 32) {
49
+
return v.err({
50
+
message: `service id too long (max 32 characters)`,
51
+
path: [id],
52
+
});
53
+
}
54
+
}
55
+
56
+
return v.ok(input);
57
+
}),
111
58
});
112
-
export type PlcUpdatePayload = v.Infer<typeof updatePayload>;
59
+
60
+
export type UpdatePayload = v.Infer<typeof updatePayload>;
-14
src/api/types/strings.ts
-14
src/api/types/strings.ts
···
1
1
import * as v from '@badrap/valita';
2
2
3
-
import { DID_KEY_RE, DID_RE, HANDLE_RE } from '../utils/strings';
4
-
5
-
export const didString = v
6
-
.string()
7
-
.assert((input): input is `did:${string}:${string}` => DID_RE.test(input), `must be a valid did`);
8
-
9
-
export const didKeyString = v
10
-
.string()
11
-
.assert((input): input is `did:key:${string}` => DID_KEY_RE.test(input), `must be a valid did:key`);
12
-
13
-
export const handleString = v.string().assert((input) => HANDLE_RE.test(input), `must be a valid handle`);
14
-
15
-
export const urlString = v.string().assert((input) => URL.canParse(input), `must be a valid url`);
16
-
17
3
export const serviceUrlString = v.string().assert((input) => {
18
4
const url = URL.parse(input);
19
5
+7
-12
src/api/utils/error.ts
+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
+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
+
};
-50
src/api/utils/strings.ts
-50
src/api/utils/strings.ts
···
1
-
import type { At, Records } from '@atcute/client/lexicons';
2
-
3
-
import { assert } from '~/lib/utils/invariant';
4
-
5
-
export const ATURI_RE =
6
-
/^at:\/\/(did:[a-zA-Z0-9._:%\-]+|[a-zA-Z0-9-.]+)\/([a-zA-Z0-9-.]+)\/([a-zA-Z0-9._~:@!$&%')(*+,;=\-]+)(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/;
7
-
8
-
export const DID_RE = /^did:([a-z]+):([a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-])$/;
9
-
10
-
export const DID_WEB_RE = /^did:web:([a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*(?:\.[a-zA-Z]{2,}))$/;
11
-
12
-
export const DID_PLC_RE = /^did:plc:([a-z2-7]{24})$/;
13
-
14
-
export const DID_KEY_RE = /^did:key:z[a-km-zA-HJ-NP-Z1-9]+$/;
15
-
16
-
export const HANDLE_RE = /^[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(?:\.[a-zA-Z]{2,})$/;
17
-
18
-
export const DID_OR_HANDLE_RE =
19
-
/^[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(?:\.[a-zA-Z]{2,})$|^did:[a-z]+:[a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-]$/;
20
-
21
-
export interface AtUri {
22
-
repo: string;
23
-
collection: string;
24
-
rkey: string;
25
-
fragment: string | undefined;
26
-
}
27
-
28
-
export const isDid = (value: string): value is At.DID => {
29
-
return value.length >= 7 && DID_RE.test(value);
30
-
};
31
-
32
-
export const isHandle = (value: string): boolean => {
33
-
return value.length >= 4 && HANDLE_RE.test(value);
34
-
};
35
-
36
-
export const parseAtUri = (str: string): AtUri => {
37
-
const match = ATURI_RE.exec(str);
38
-
assert(match !== null, `Failed to parse AT URI for ${str}`);
39
-
40
-
return {
41
-
repo: match[1],
42
-
collection: match[2],
43
-
rkey: match[3],
44
-
fragment: match[4],
45
-
};
46
-
};
47
-
48
-
export const makeAtUri = (repo: string, collection: keyof Records | (string & {}), rkey: string) => {
49
-
return `at://${repo}/${collection}/${rkey}`;
50
-
};
+26
src/api/utils/valita.ts
+26
src/api/utils/valita.ts
···
1
+
import * as v from '@badrap/valita';
2
+
3
+
export type ToValidator<T> = T extends readonly [infer Head, ...infer Rest]
4
+
? Rest extends []
5
+
? v.TupleType<[ToValidator<Head>]>
6
+
: v.TupleType<[ToValidator<Head>, ...ToValidatorTuple<Rest>]>
7
+
: T extends ReadonlyArray<infer E>
8
+
? v.ArrayType<ToValidator<E>>
9
+
: T extends object
10
+
? ToObjectValidator<T>
11
+
: v.Type<T>;
12
+
13
+
// Helper type for converting tuple types
14
+
type ToValidatorTuple<T extends readonly unknown[]> = T extends readonly [infer Head, ...infer Rest]
15
+
? Rest extends []
16
+
? [ToValidator<Head>]
17
+
: [ToValidator<Head>, ...ToValidatorTuple<Rest>]
18
+
: [];
19
+
20
+
// Helper type for converting object types
21
+
type ToObjectValidator<T extends object> = v.ObjectType<
22
+
{
23
+
[K in keyof T]-?: undefined extends T[K] ? v.Optional<Exclude<T[K], undefined>> : ToValidator<T[K]>;
24
+
},
25
+
undefined
26
+
>;
+72
src/components/accordion.tsx
+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
+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;
+9
src/components/ic-icons/baseline-chevron-right.tsx
+9
src/components/ic-icons/baseline-chevron-right.tsx
+12
src/components/ic-icons/baseline-close.tsx
+12
src/components/ic-icons/baseline-close.tsx
···
1
+
import { createIcon } from './_icon';
2
+
3
+
const CloseIcon = createIcon(() => (
4
+
<svg width="1em" height="1em" viewBox="0 0 24 24">
5
+
<path
6
+
fill="currentColor"
7
+
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z"
8
+
/>
9
+
</svg>
10
+
));
11
+
12
+
export default CloseIcon;
+2
-3
src/components/inputs/multiline-input.tsx
+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}
+6
-6
src/components/inputs/radio-input.tsx
+6
-6
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;
···
10
10
name?: string;
11
11
required?: boolean;
12
12
value?: T;
13
-
options: { value: NoInfer<T>; label: string }[];
13
+
options: { value: NoInfer<T>; label: string; disabled?: boolean }[];
14
14
onChange?: (next: NoInfer<T>, event: BoundInputEvent<HTMLInputElement>) => void;
15
15
}
16
16
···
26
26
<span class="font-semibold text-gray-600">{props.label}</span>
27
27
</legend>
28
28
29
-
{props.options.map(({ value, label }, idx) => {
29
+
{props.options.map(({ value, label, disabled }, idx) => {
30
30
const optionId = fieldId + idx;
31
31
32
32
return (
33
-
<span class="flex items-center gap-3">
33
+
<fieldset disabled={disabled} class="flex items-center gap-3 disabled:opacity-50">
34
34
<input
35
35
type="radio"
36
36
id={optionId}
···
45
45
<label for={optionId} class="text-sm">
46
46
{label}
47
47
</label>
48
-
</span>
48
+
</fieldset>
49
49
);
50
50
})}
51
51
+2
-2
src/components/inputs/select-input.tsx
+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
+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
+1
-1
src/components/inputs/toggle-input.tsx
+22
src/components/page-header.tsx
+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
-1
src/components/wizard.tsx
+18
-17
src/components/wizards/bluesky-login-step.tsx
+18
-17
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 { At } from '@atcute/client/lexicons';
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';
8
-
import { DidDocument, getPdsEndpoint } from '~/api/types/did-doc';
9
9
import { formatTotpCode, TOTP_RE } from '~/api/utils/auth';
10
-
import { isDid } from '~/api/utils/strings';
11
10
12
11
import { createMutation } from '~/lib/utils/mutation';
13
12
···
54
53
service = service?.trim() || undefined;
55
54
56
55
if (service === undefined) {
57
-
let did: At.DID;
58
-
if (!isDid(identifier)) {
56
+
let did: AtprotoDid;
57
+
if (isAtprotoDid(identifier)) {
58
+
did = identifier;
59
+
} else if (isHandle(identifier)) {
59
60
did = await resolveHandleViaAppView({ handle: identifier });
60
61
} else {
61
-
did = identifier;
62
+
throw new InsufficientLoginError(`Invalid identifier`);
62
63
}
63
64
64
65
const didDoc = await getDidDocument({ did });
···
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
+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 }) });
+104
-4
src/lib/utils/confirmation-code.ts
+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
};
+7
src/lib/utils/mutation.ts
+7
src/lib/utils/mutation.ts
···
49
49
) & {
50
50
mutate(variables: V): void;
51
51
mutateAsync(variables: V): Promise<D>;
52
+
reset(): void;
52
53
};
53
54
54
55
type MutationFunction<D = unknown, V = unknown> = (variables: V, signal: AbortSignal) => Promise<D>;
···
74
75
{ s: MutationState.IDLE },
75
76
{ equals: (prev, next) => prev.s === next.s },
76
77
);
78
+
79
+
const reset = () => {
80
+
cleanup();
81
+
setState({ s: MutationState.IDLE });
82
+
};
77
83
78
84
const mutate = async (variables: V): Promise<D> => {
79
85
const signal = getSignal();
···
139
145
},
140
146
mutateAsync: mutate,
141
147
mutate: (variables: V) => mutate(variables).then(noop, noop),
148
+
reset: reset,
142
149
} as any;
143
150
};
144
151
+18
-6
src/lib/utils/promise-queue.ts
+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()));
+6
-7
src/lib/utils/search-params.ts
+6
-7
src/lib/utils/search-params.ts
···
1
1
import { batch, createSignal } from 'solid-js';
2
2
3
-
import { At } from '@atcute/client/lexicons';
3
+
import { isDid, isHandle } from '@atcute/lexicons/syntax';
4
4
5
-
import { DID_OR_HANDLE_RE, DID_RE, HANDLE_RE } from '~/api/utils/strings';
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' && DID_RE.test(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' && HANDLE_RE.test(value)) {
238
+
if (isHandle(value)) {
240
239
return value;
241
240
}
242
241
···
249
248
250
249
export const asIdentifier = createParser({
251
250
parse(value) {
252
-
if (typeof value === 'string' && DID_OR_HANDLE_RE.test(value)) {
251
+
if (typeof value === 'string' && (isDid(value) || isHandle(value))) {
253
252
return value;
254
253
}
255
254
+17
src/lib/utils/stream.ts
+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
+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);
+15
-2
src/routes.ts
+15
-2
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',
···
41
45
component: lazy(() => import('./views/repository/repo-export')),
42
46
},
43
47
{
44
-
path: '/car-unpack',
45
-
component: lazy(() => import('./views/repository/car-unpack')),
48
+
path: '/repo-archive-unpack',
49
+
component: lazy(() => import('./views/repository/repo-archive-unpack')),
50
+
},
51
+
{
52
+
path: '/repo-archive-explore',
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')),
46
59
},
47
60
48
61
{
+49
src/views/account/account-migrate/context.tsx
+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
+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
+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
+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
+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
+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
+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
+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
+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;
+58
-41
src/views/blob/blob-export.tsx
+58
-41
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 { At } from '@atcute/client/lexicons';
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
-
import { getPdsEndpoint } from '~/api/types/did-doc';
11
11
import { isServiceUrlString } from '~/api/types/strings';
12
-
import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings';
13
12
14
13
import { useTitle } from '~/lib/navigation/router';
15
14
import { makeAbortable } from '~/lib/utils/abortable';
···
18
17
import Button from '~/components/inputs/button';
19
18
import TextInput from '~/components/inputs/text-input';
20
19
import Logger, { createLogger } from '~/components/logger';
20
+
import PageHeader from '~/components/page-header';
21
21
22
22
const BlobExportPage = () => {
23
23
const logger = createLogger();
···
36
36
}) => {
37
37
logger.info(`Starting export for ${identifier}`);
38
38
39
-
let did: At.DID;
40
-
if (isDid(identifier)) {
39
+
let did: AtprotoDid;
40
+
if (isAtprotoDid(identifier)) {
41
41
did = identifier;
42
-
} else if (service) {
43
-
did = await resolveHandleViaPds({ service, handle: identifier, signal });
44
-
logger.log(`Resolved handle to ${did}`);
42
+
} else if (isHandle(identifier)) {
43
+
if (service) {
44
+
did = await resolveHandleViaPds({ service, handle: identifier, signal });
45
+
logger.log(`Resolved handle to ${did}`);
46
+
} else {
47
+
did = await resolveHandleViaAppView({ handle: identifier, signal });
48
+
logger.log(`Resolved handle to ${did}`);
49
+
}
45
50
} else {
46
-
did = await resolveHandleViaAppView({ handle: identifier, signal });
47
-
logger.log(`Resolved handle to ${did}`);
51
+
logger.error(`Invalid identifier`);
52
+
return;
48
53
}
49
54
50
55
if (!service) {
···
61
66
service = endpoint;
62
67
}
63
68
64
-
const rpc = new XRPC({ handler: simpleFetchHandler({ service }) });
69
+
// const rpc = new XRPC({ handler: simpleFetchHandler({ service }) });
70
+
const client = new Client({ handler: simpleFetchHandler({ service }) });
65
71
66
72
// Grab a list of blobs
67
73
let blobs: string[] = [];
···
70
76
71
77
let cursor: string | undefined;
72
78
do {
73
-
const { data } = await rpc.get('com.atproto.sync.listBlobs', {
74
-
signal,
75
-
params: { did, cursor, limit: 1_000 },
76
-
});
79
+
const data = await ok(
80
+
client.get('com.atproto.sync.listBlobs', {
81
+
signal,
82
+
params: { did, cursor, limit: 1_000 },
83
+
}),
84
+
);
77
85
78
86
cursor = data.cursor;
79
87
blobs = blobs.concat(data.cids);
···
147
155
attempts++;
148
156
149
157
try {
150
-
const { data } = await rpc.get('com.atproto.sync.getBlob', {
158
+
const response = await client.get('com.atproto.sync.getBlob', {
151
159
signal,
160
+
as: 'bytes',
152
161
params: { did, cid },
153
162
});
154
163
155
-
return data;
156
-
} catch (err) {
157
-
if (attempts > 3) {
158
-
throw err;
164
+
if (response.ok) {
165
+
return response.data;
159
166
}
160
167
161
-
if (err instanceof XRPCError) {
162
-
if (err.status === 400) {
163
-
if (err.message === 'Blob not found') {
164
-
console.warn(`Blob ${cid} not found`);
165
-
return;
166
-
}
167
-
} else if (err.status === 429) {
168
-
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');
169
177
170
-
if (reset !== undefined) {
171
-
logger.warn(`Ratelimit exceeded when downloading ${cid}, waiting`);
178
+
logger.warn(`Ratelimit exceeded when downloading ${cid}, waiting`);
172
179
173
-
const refreshAt = +reset * 1_000;
174
-
const delta = refreshAt - Date.now();
180
+
if (reset !== null) {
181
+
const refreshAt = +reset * 1_000;
182
+
const delta = refreshAt - Date.now();
175
183
176
-
await sleep(delta);
177
-
}
184
+
await sleep(delta);
185
+
} else {
186
+
await sleep(10_000);
178
187
}
179
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;
180
202
}
181
203
}
182
204
};
···
208
230
209
231
return (
210
232
<>
211
-
<div class="p-4">
212
-
<h1 class="text-lg font-bold text-purple-800">Export blobs</h1>
213
-
<p class="text-gray-600">Download all blobs from an account into a tarball</p>
214
-
</div>
215
-
<hr class="mx-4 border-gray-300" />
233
+
<PageHeader title="Export blobs" subtitle="Download all blobs from an account into a tarball" />
216
234
217
235
<form
218
236
onSubmit={(ev) => {
···
263
281
type="text"
264
282
name="ident"
265
283
autocomplete="username"
266
-
pattern={/* @once */ DID_OR_HANDLE_RE.source}
267
284
placeholder="paul.bsky.social"
268
285
autofocus
269
286
/>
+10
-11
src/views/bluesky/threadgate-applicator/page.tsx
+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 type { DidDocument } from '@atcute/identity';
5
6
6
-
import { DidDocument } from '~/api/types/did-doc';
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"
+23
-18
src/views/bluesky/threadgate-applicator/steps/step1_handle-input.tsx
+23
-18
src/views/bluesky/threadgate-applicator/steps/step1_handle-input.tsx
···
1
1
import { createSignal } from 'solid-js';
2
2
3
-
import type { AppBskyFeedThreadgate, At } from '@atcute/client/lexicons';
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';
4
7
5
8
import { getDidDocument } from '~/api/queries/did-doc';
6
9
import { resolveHandleViaAppView } from '~/api/queries/handle';
7
-
import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings';
8
10
9
11
import { appViewRpc } from '~/globals/rpc';
10
12
···
12
14
13
15
import Button from '~/components/inputs/button';
14
16
import TextInput from '~/components/inputs/text-input';
15
-
import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard';
17
+
import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard';
16
18
17
-
import { ThreadgateApplicatorConstraints, ThreadgateState, ThreadItem } from '../page';
19
+
import type { ThreadgateApplicatorConstraints, ThreadgateState, ThreadItem } from '../page';
18
20
import { sortThreadgateState } from '../utils';
19
21
20
22
class NoThreadsError extends Error {}
···
32
34
async mutationFn({ identifier }: { identifier: string }, signal) {
33
35
setStatus(`Resolving identity`);
34
36
35
-
let did: At.DID;
36
-
if (isDid(identifier)) {
37
+
let did: AtprotoDid;
38
+
if (isAtprotoDid(identifier)) {
37
39
did = identifier;
40
+
} else if (isHandle(identifier)) {
41
+
did = await resolveHandleViaAppView({ handle: identifier, signal });
38
42
} else {
39
-
did = await resolveHandleViaAppView({ handle: identifier, signal });
43
+
throw new Error(`Invalid identifier`);
40
44
}
41
45
42
46
const didDoc = await getDidDocument({ did, signal });
···
47
51
48
52
let cursor: string | undefined;
49
53
do {
50
-
const { data } = await appViewRpc.get('app.bsky.feed.getAuthorFeed', {
51
-
signal,
52
-
params: {
53
-
actor: did,
54
-
filter: 'posts_no_replies',
55
-
limit: 100,
56
-
cursor,
57
-
},
58
-
});
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
+
);
59
65
60
66
cursor = data.cursor;
61
67
···
80
86
let threadgate: ThreadgateState | null = null;
81
87
82
88
if (tg?.record) {
83
-
const record = tg.record as AppBskyFeedThreadgate.Record;
89
+
const record = tg.record as AppBskyFeedThreadgate.Main;
84
90
85
91
const allow = record?.allow;
86
92
const hiddenReplies = record?.hiddenReplies;
···
150
156
placeholder="paul.bsky.social"
151
157
value={identifier()}
152
158
required
153
-
pattern={/* @once */ DID_OR_HANDLE_RE.source}
154
159
autofocus={isActive()}
155
160
onChange={setIdentifier}
156
161
/>
+19
-26
src/views/bluesky/threadgate-applicator/steps/step2_rules-input.tsx
+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
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,
+71
-52
src/views/bluesky/threadgate-applicator/steps/step4_confirmation.tsx
+71
-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 { 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
8
7
9
import { dequal } from '~/lib/utils/dequal';
···
9
11
10
12
import Button from '~/components/inputs/button';
11
13
import ToggleInput from '~/components/inputs/toggle-input';
12
-
import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard';
13
-
14
-
import { parseAtUri } from '~/api/utils/strings';
15
14
import Logger, { createLogger } from '~/components/logger';
16
-
import { ThreadgateApplicatorConstraints } from '../page';
15
+
import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard';
16
+
17
+
import type { ThreadgateApplicatorConstraints } from '../page';
17
18
18
19
const Step4_Confirmation = ({
19
20
data,
···
33
34
logger.log(`Preparing writes`);
34
35
35
36
const rules = data.rules;
36
-
const writes: ComAtprotoRepoApplyWrites.Input['writes'] = [];
37
+
const writes: ComAtprotoRepoApplyWrites.$input['writes'] = [];
37
38
38
39
const now = new Date().toISOString();
39
40
for (const { post, threadgate } of data.threads) {
40
41
if (threadgate === null) {
41
42
if (rules !== undefined) {
42
-
const { rkey } = parseAtUri(post.uri);
43
+
const postUri = parseCanonicalResourceUri(post.uri);
44
+
if (!postUri.ok) {
45
+
throw new Error(`failed to parse ${post.uri}`);
46
+
}
43
47
44
-
const record: AppBskyFeedThreadgate.Record = {
48
+
const record: AppBskyFeedThreadgate.Main = {
45
49
$type: 'app.bsky.feed.threadgate',
46
50
createdAt: now,
47
51
post: post.uri,
···
52
56
writes.push({
53
57
$type: 'com.atproto.repo.applyWrites#create',
54
58
collection: 'app.bsky.feed.threadgate',
55
-
rkey: rkey,
59
+
rkey: postUri.value.rkey,
56
60
value: record,
57
61
});
58
62
}
59
63
} else {
60
64
if (rules === undefined && !threadgate.hiddenReplies?.length) {
61
-
const { rkey } = parseAtUri(threadgate.uri);
65
+
const threadgateUri = parseCanonicalResourceUri(threadgate.uri);
66
+
if (!threadgateUri.ok) {
67
+
throw new Error(`failed to parse ${threadgate.uri}`);
68
+
}
62
69
63
70
writes.push({
64
71
$type: 'com.atproto.repo.applyWrites#delete',
65
72
collection: 'app.bsky.feed.threadgate',
66
-
rkey: rkey,
73
+
rkey: threadgateUri.value.rkey,
67
74
});
68
75
} else if (!dequal(threadgate.allow, rules)) {
69
-
const { rkey } = parseAtUri(threadgate.uri);
76
+
const threadgateUri = parseCanonicalResourceUri(threadgate.uri);
77
+
if (!threadgateUri.ok) {
78
+
throw new Error(`failed to parse ${threadgate.uri}`);
79
+
}
70
80
71
-
const record: AppBskyFeedThreadgate.Record = {
81
+
const record: AppBskyFeedThreadgate.Main = {
72
82
$type: 'app.bsky.feed.threadgate',
73
83
createdAt: threadgate.createdAt,
74
84
post: post.uri,
···
79
89
writes.push({
80
90
$type: 'com.atproto.repo.applyWrites#update',
81
91
collection: 'app.bsky.feed.threadgate',
82
-
rkey: rkey,
92
+
rkey: threadgateUri.value.rkey,
83
93
value: record,
84
94
});
85
95
}
···
89
99
logger.log(`${writes.length} write operations to apply`);
90
100
91
101
const did = data.profile.didDoc.id;
92
-
const rpc = new XRPC({ handler: data.manager });
93
-
94
-
const RATELIMIT_POINT_LIMIT = 150 * 3;
102
+
const client = new Client({ handler: data.manager });
95
103
96
104
{
97
105
using progress = logger.progress(`Applying writes`);
98
106
99
107
let written = 0;
100
108
for (const chunk of chunked(writes, 200)) {
101
-
try {
102
-
const { headers } = await rpc.call('com.atproto.repo.applyWrites', {
103
-
data: {
104
-
repo: did,
105
-
writes: chunk,
106
-
},
107
-
});
109
+
let attempts = 0;
108
110
109
-
written += chunk.length;
110
-
progress.update(`Applying writes (${written} applied)`);
111
+
while (true) {
112
+
if (attempts > 0) {
113
+
await sleep(2_000);
114
+
}
111
115
112
-
if ('ratelimit-remaining' in headers) {
113
-
const remaining = +headers['ratelimit-remaining'];
114
-
const reset = +headers['ratelimit-reset'] * 1_000;
116
+
attempts++;
115
117
116
-
if (remaining < RATELIMIT_POINT_LIMIT) {
117
-
// add some delay to be sure
118
-
const delta = reset - Date.now() + 5_000;
119
-
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
+
});
120
125
121
-
await new Promise((resolve) => setTimeout(resolve, delta));
126
+
if (response.ok) {
127
+
written += chunk.length;
128
+
progress.update(`Applying writes (${written} applied)`);
129
+
break;
122
130
}
123
-
}
124
-
} catch (err) {
125
-
if (!(err instanceof XRPCError) || err.kind !== 'RateLimitExceeded') {
126
-
throw err;
127
-
}
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();
128
141
129
-
const headers = err.headers;
130
-
if ('ratelimit-remaining' in headers) {
131
-
const remaining = +headers['ratelimit-remaining'];
132
-
const reset = +headers['ratelimit-reset'] * 1_000;
142
+
await sleep(delta);
143
+
} else {
144
+
await sleep(10_000);
145
+
}
146
+
}
133
147
134
-
if (remaining < RATELIMIT_POINT_LIMIT) {
135
-
// add some delay to be sure
136
-
const delta = reset - Date.now() + 5_000;
137
-
using _progress = logger.progress(`Reached ratelimit, waiting ${delta}ms`);
148
+
if (attempts < 3) {
149
+
continue;
150
+
}
138
151
139
-
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;
140
157
}
141
-
} else {
142
-
using _progress = logger.progress(`Reached ratelimit, waiting 1 minute`);
143
158
144
-
await new Promise((resolve) => setTimeout(resolve, 60 * 1_000));
159
+
throw err;
145
160
}
146
161
}
147
162
}
···
201
216
};
202
217
203
218
export default Step4_Confirmation;
219
+
220
+
const sleep = (ms: number): Promise<void> => {
221
+
return new Promise((resolve) => setTimeout(resolve, ms));
222
+
};
+5
-4
src/views/bluesky/threadgate-applicator/steps/step5_finished.tsx
+5
-4
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 (
7
7
<Stage title="All done!">
8
8
<div>
9
-
<p class="text-pretty">Thread gating option has been applied.</p>
9
+
<p class="text-pretty">Thread gating has been applied.</p>
10
10
11
11
<p class="mt-3 text-pretty">
12
-
You can close this page, or reload the page if you intend on doing another submission.
12
+
You can revoke the app password and close this page now, or reload the page if you intend on
13
+
changing your mind.
13
14
</p>
14
15
</div>
15
16
</Stage>
+1
-1
src/views/bluesky/threadgate-applicator/utils.ts
+1
-1
src/views/bluesky/threadgate-applicator/utils.ts
+3
-6
src/views/crypto/crypto-generate.tsx
+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
+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;
+11
-13
src/views/frontpage.tsx
+11
-13
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';
···
61
63
icon: ArchiveOutlinedIcon,
62
64
},
63
65
{
64
-
name: `Unpack CAR file`,
66
+
name: `Unpack archive`,
65
67
description: `Extract a repository archive into a tarball`,
66
-
href: '/car-unpack',
68
+
href: '/repo-archive-unpack',
67
69
icon: DirectionsCarOutlinedIcon,
68
70
},
69
71
{
70
-
name: `Repository explorer`,
71
-
description: `Explore an account's public records`,
72
-
href: null,
72
+
name: `Explore archive`,
73
+
description: `Explore a repository archive`,
74
+
href: '/repo-archive-explore',
73
75
icon: ExploreOutlinedIcon,
74
76
},
75
77
],
···
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
+24
-38
src/views/identity/did-lookup.tsx
+24
-38
src/views/identity/did-lookup.tsx
···
1
1
import { Match, Switch } from 'solid-js';
2
2
3
-
import { At } from '@atcute/client/lexicons';
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, isDid } 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({
···
24
25
const query = createQuery(
25
26
() => params.q,
26
27
async (identifier, signal) => {
27
-
let did: At.DID;
28
-
if (isDid(identifier)) {
28
+
let did: AtprotoDid;
29
+
if (isAtprotoDid(identifier)) {
29
30
did = identifier;
31
+
} else if (isHandle(identifier)) {
32
+
did = await resolveHandleViaAppView({ handle: identifier, signal });
30
33
} else {
31
-
did = await resolveHandleViaAppView({ handle: identifier, signal });
34
+
throw new Error(`Invalid identifier`);
32
35
}
33
36
34
37
const doc = await getDidDocument({ did, signal });
···
44
47
45
48
return (
46
49
<>
47
-
<div class="p-4">
48
-
<h1 class="text-lg font-bold text-purple-800">View identity info</h1>
49
-
<p class="text-gray-600">Look up an account's DID document</p>
50
-
</div>
51
-
<hr class="mx-4 border-gray-300" />
50
+
<PageHeader title="View identity info" subtitle="Look up an account's DID document" />
52
51
53
52
<form
54
53
onSubmit={(ev) => {
55
54
const formData = new FormData(ev.currentTarget);
56
55
ev.preventDefault();
57
56
58
-
const ident = formData.get('ident') as string;
57
+
const ident = formData.get('ident') as Did | Handle;
59
58
setParams({ q: ident });
60
59
}}
61
60
class="m-4 flex flex-col gap-4"
···
65
64
type="text"
66
65
name="ident"
67
66
autocomplete="username"
68
-
pattern={/* @once */ DID_OR_HANDLE_RE.source}
69
67
placeholder="paul.bsky.social"
70
68
autofocus
71
69
/>
···
100
98
<div>
101
99
<p class="font-semibold text-gray-600">Identifies as</p>
102
100
<ol class="list-disc pl-4">
103
-
{doc.alsoKnownAs.map((ident) => (
101
+
{doc.alsoKnownAs?.map((ident) => (
104
102
<li>{ident}</li>
105
103
))}
106
104
</ol>
···
109
107
<div>
110
108
<p class="font-semibold text-gray-600">Services</p>
111
109
<ol class="list-disc pl-4">
112
-
{doc.service.map(({ id, type, serviceEndpoint }, idx) => {
110
+
{doc.service?.map(({ id, type, serviceEndpoint }, idx) => {
113
111
const isString = typeof serviceEndpoint === 'string';
114
112
const isURL = isString && URL.canParse('' + serviceEndpoint);
115
113
const isServiceUrl = isString && isServiceUrlString(serviceEndpoint);
···
132
130
133
131
<div class="mt-2 flex flex-wrap gap-2 empty:hidden">
134
132
{isPDS && isServiceUrl && (
135
-
<button
136
-
disabled
137
-
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"
138
-
>
133
+
<Button variant="outline" disabled>
139
134
View PDS info
140
-
</button>
135
+
</Button>
141
136
)}
142
137
143
138
{isPDS && isServiceUrl && (
144
-
<button
145
-
disabled
146
-
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"
147
-
>
139
+
<Button variant="outline" disabled>
148
140
Explore account repository
149
-
</button>
141
+
</Button>
150
142
)}
151
143
152
144
{isLabeler && isServiceUrl && (
153
-
<button
154
-
disabled
155
-
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"
156
-
>
145
+
<Button variant="outline" disabled>
157
146
View emitted labels
158
-
</button>
147
+
</Button>
159
148
)}
160
149
</div>
161
150
</li>
···
167
156
<div>
168
157
<p class="font-semibold text-gray-600">Verification methods</p>
169
158
<ol class="list-disc pl-4">
170
-
{doc.verificationMethod.map(({ id, type, publicKeyMultibase }, idx) => {
159
+
{doc.verificationMethod?.map(({ id, type, publicKeyMultibase }, idx) => {
171
160
return (
172
161
<li class={idx !== 0 ? `mt-3` : ``}>
173
162
<p class="font-medium">{id.replace(doc.id, '')}</p>
···
184
173
</div>
185
174
186
175
<div class="flex flex-wrap gap-4 p-4 pt-2">
187
-
<button
176
+
<Button
177
+
variant="outline"
188
178
onClick={() => {
189
179
navigator.clipboard.writeText(JSON.stringify(doc, null, 2));
190
180
}}
191
-
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"
192
181
>
193
182
Copy DID document
194
-
</button>
183
+
</Button>
195
184
196
185
{isDidPlc && (
197
-
<a
198
-
href={`/plc-oplogs?q=${params.q!}`}
199
-
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"
200
-
>
186
+
<Button variant="outline" href={`/plc-oplogs?q=${params.q!}`}>
201
187
View PLC operation logs
202
-
</a>
188
+
</Button>
203
189
)}
204
190
</div>
205
191
</>
+15
-18
src/views/identity/plc-applicator/page.tsx
+15
-18
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
+
import type { CompatibleOperation, IndexedEntry, IndexedEntryWithSigner } from '@atcute/did-plc';
7
+
import type { DidDocument } from '@atcute/identity';
8
+
import type { Did } from '@atcute/lexicons/syntax';
6
9
7
-
import type { DidDocument } from '~/api/types/did-doc';
8
-
import type { PlcUpdatePayload } from '~/api/types/plc';
10
+
import type { UpdatePayload } from '~/api/types/plc';
9
11
10
12
import { history } from '~/globals/navigation';
11
13
12
14
import { useTitle } from '~/lib/navigation/router';
13
15
16
+
import PageHeader from '~/components/page-header';
14
17
import { Wizard } from '~/components/wizard';
15
18
16
-
import type { DetailedPlcEntry } from './plc-utils';
17
-
18
19
import Step1_HandleInput from './steps/step1_handle-input';
19
20
import Step2_PdsAuthentication from './steps/step2_pds-authentication';
20
21
import Step2_PrivateKeyInput from './steps/step2_private-key-input';
···
26
27
27
28
export interface PlcInformation {
28
29
didDoc: DidDocument;
29
-
logs: DetailedPlcEntry[];
30
+
logs: IndexedEntryWithSigner[];
30
31
}
31
32
32
33
export interface PdsSigningMethod {
33
34
type: 'pds';
34
35
manager: CredentialManager;
35
-
recommendedDidDoc: ComAtprotoIdentityGetRecommendedDidCredentials.Output;
36
+
recommendedDidDoc: ComAtprotoIdentityGetRecommendedDidCredentials.$output;
36
37
}
37
38
38
39
export type Keypair = P256PrivateKey | Secp256k1PrivateKey;
39
40
export interface PrivateKeySigningMethod {
40
41
type: 'private_key';
41
42
keypair: Keypair;
42
-
didPublicKey: string;
43
+
didPublicKey: Did<'key'>;
43
44
}
44
45
45
46
export type SigningMethod = PdsSigningMethod | PrivateKeySigningMethod;
···
62
63
Step4_PayloadInput: {
63
64
info: PlcInformation;
64
65
method: SigningMethod;
65
-
base: DetailedPlcEntry;
66
+
base: IndexedEntry<CompatibleOperation>;
66
67
};
67
68
68
69
Step5_PdsConfirmation: {
69
70
info: PlcInformation;
70
71
method: PdsSigningMethod;
71
-
base: DetailedPlcEntry;
72
-
payload: PlcUpdatePayload;
72
+
base: IndexedEntry<CompatibleOperation>;
73
+
payload: UpdatePayload;
73
74
};
74
75
Step5_PrivateKeyConfirmation: {
75
76
info: PlcInformation;
76
77
method: PrivateKeySigningMethod;
77
-
base: DetailedPlcEntry;
78
-
payload: PlcUpdatePayload;
78
+
base: IndexedEntry<CompatibleOperation>;
79
+
payload: UpdatePayload;
79
80
};
80
81
81
82
Step6_Finished: {};
···
101
102
102
103
return (
103
104
<>
104
-
<div class="p-4">
105
-
<h1 class="text-lg font-bold text-purple-800">Apply PLC operations</h1>
106
-
<p class="text-gray-600">Submit operations to your did:plc identity</p>
107
-
</div>
108
-
<hr class="mx-4 border-gray-300" />
105
+
<PageHeader title="Apply PLC operations" subtitle="Submit operations to your did:plc identity" />
109
106
110
107
<Wizard<PlcApplicatorConstraints>
111
108
initialStep="Step1_HandleInput"
+3
-96
src/views/identity/plc-applicator/plc-utils.ts
+3
-96
src/views/identity/plc-applicator/plc-utils.ts
···
1
-
import * as CBOR from '@atcute/cbor';
2
-
import { verifySigWithDidKey } from '@atcute/crypto';
3
-
import { fromBase64Url } from '@atcute/multibase';
1
+
import type { IndexedEntry } from '@atcute/did-plc';
4
2
5
-
import { PlcLogEntry, PlcUpdatePayload } from '~/api/types/plc';
6
-
import { UnwrapArray } from '~/api/utils/types';
3
+
import type { UpdatePayload } from '~/api/types/plc';
7
4
8
5
import { assert } from '~/lib/utils/invariant';
9
6
10
-
export const getPlcPayload = (entry: PlcLogEntry): PlcUpdatePayload => {
7
+
export const getPlcPayload = (entry: IndexedEntry): UpdatePayload => {
11
8
const op = entry.operation;
12
9
assert(op.type === 'plc_operation' || op.type === 'create');
13
10
···
36
33
37
34
assert(false);
38
35
};
39
-
40
-
export const getPlcKeying = async (logs: PlcLogEntry[]) => {
41
-
logs = logs.filter((entry) => !entry.nullified);
42
-
43
-
const length = logs.length;
44
-
const promises = logs.map(async (entry, idx) => {
45
-
const operation = entry.operation;
46
-
if (operation.type === 'plc_tombstone') {
47
-
return;
48
-
}
49
-
50
-
// If it's not the last entry, check if the next entry ahead of this one
51
-
// was made within the last 72 hours.
52
-
if (idx !== length - 1) {
53
-
const next = logs[idx + 1]!;
54
-
const date = new Date(next.createdAt);
55
-
const diff = Date.now() - date.getTime();
56
-
57
-
if (diff / (1_000 * 60 * 60) > 72) {
58
-
return;
59
-
}
60
-
}
61
-
62
-
/** keys that potentially signed this operation */
63
-
let signers: `did:key:${string}`[] | undefined;
64
-
if (operation.prev === null) {
65
-
if (operation.type === 'create') {
66
-
signers = [operation.recoveryKey, operation.signingKey];
67
-
} else if (operation.type === 'plc_operation') {
68
-
signers = operation.rotationKeys;
69
-
}
70
-
} else {
71
-
const prev = logs[idx - 1];
72
-
assert(prev !== undefined, `missing previous entry from ${entry.createdAt}`);
73
-
assert(prev.cid === operation.prev, `prev cid mismatch on ${entry.createdAt}`);
74
-
75
-
const prevOp = prev.operation;
76
-
77
-
if (prevOp.type === 'create') {
78
-
signers = [prevOp.recoveryKey, prevOp.signingKey];
79
-
} else if (prevOp.type === 'plc_operation') {
80
-
signers = prevOp.rotationKeys;
81
-
}
82
-
}
83
-
84
-
assert(signers !== undefined, `no signers found for ${entry.createdAt}`);
85
-
86
-
const opBytes = CBOR.encode({ ...operation, sig: undefined });
87
-
const sigBytes = fromBase64Url(operation.sig);
88
-
89
-
/** key that signed this operation */
90
-
let signedBy: string | undefined;
91
-
for (const key of signers) {
92
-
const valid = await verifySigWithDidKey(key, sigBytes, opBytes);
93
-
if (valid) {
94
-
signedBy = key;
95
-
break;
96
-
}
97
-
}
98
-
99
-
assert(signedBy !== undefined, `no valid signer for ${entry.createdAt}`);
100
-
101
-
return {
102
-
...entry,
103
-
signers,
104
-
signedBy,
105
-
};
106
-
});
107
-
108
-
const fulfilled = await Promise.all(promises);
109
-
return fulfilled.filter((entry) => entry !== undefined);
110
-
};
111
-
112
-
type DetailedEntries = Awaited<ReturnType<typeof getPlcKeying>>;
113
-
export type DetailedPlcEntry = UnwrapArray<DetailedEntries>;
114
-
115
-
export const getCurrentSignersFromEntry = (entry: PlcLogEntry): string[] => {
116
-
const operation = entry.operation;
117
-
118
-
/** keys that can sign the next operation */
119
-
let nextSigners: string[] | undefined;
120
-
if (operation.type === 'create') {
121
-
nextSigners = [operation.recoveryKey, operation.signingKey];
122
-
} else if (operation.type === 'plc_operation') {
123
-
nextSigners = operation.rotationKeys;
124
-
}
125
-
126
-
assert(nextSigners !== undefined, `no signers found for ${entry.createdAt}`);
127
-
return nextSigners;
128
-
};
+27
-22
src/views/identity/plc-applicator/steps/step1_handle-input.tsx
+27
-22
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';
4
-
import { At } from '@atcute/client/lexicons';
3
+
import { ClientResponseError } from '@atcute/client';
4
+
import { processIndexedEntryLog } from '@atcute/did-plc';
5
+
import { isPlcDid } from '@atcute/identity';
6
+
import { isHandle, type Did } from '@atcute/lexicons/syntax';
5
7
6
8
import { getDidDocument } from '~/api/queries/did-doc';
7
9
import { resolveHandleViaAppView } from '~/api/queries/handle';
8
10
import { getPlcAuditLogs } from '~/api/queries/plc';
9
-
import { DID_OR_HANDLE_RE, DID_PLC_RE, isDid } from '~/api/utils/strings';
10
11
11
12
import { createMutation } from '~/lib/utils/mutation';
12
13
13
14
import Button from '~/components/inputs/button';
14
15
import RadioInput from '~/components/inputs/radio-input';
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 { type PlcInformation, PlcApplicatorConstraints } from '../page';
19
-
import { getPlcKeying } from '../plc-utils';
19
+
import type { PlcApplicatorConstraints, PlcInformation } from '../page';
20
20
21
21
type Method = 'pds' | 'key';
22
22
···
38
38
39
39
const mutation = createMutation({
40
40
async mutationFn({ identifier }: MutationVariables): Promise<PlcInformation> {
41
-
let did: At.DID;
42
-
if (isDid(identifier)) {
41
+
let did: Did<'plc'>;
42
+
if (isPlcDid(identifier)) {
43
43
did = identifier;
44
+
} else if (isHandle(identifier)) {
45
+
const resolved = await resolveHandleViaAppView({ handle: identifier });
46
+
if (!isPlcDid(resolved)) {
47
+
throw new DidIsNotPlcError(`${resolved} does not resolve to a did:plc`);
48
+
}
49
+
50
+
did = resolved;
44
51
} else {
45
-
did = await resolveHandleViaAppView({ handle: identifier });
46
-
}
47
-
48
-
if (!DID_PLC_RE.test(did)) {
49
-
throw new DidIsNotPlcError(`"${did}" is not did:plc`);
52
+
throw new DidIsNotPlcError(`${identifier} is not a valid did:plc or handle`);
50
53
}
51
54
52
55
const [didDoc, logs] = await Promise.all([getDidDocument({ did }), getPlcAuditLogs({ did })]);
56
+
const { canonical } = await processIndexedEntryLog(did, logs);
53
57
54
58
return {
55
-
didDoc,
56
-
logs: await getPlcKeying(logs),
59
+
didDoc: didDoc,
60
+
logs: canonical,
57
61
};
58
62
},
59
63
onMutate() {
···
66
70
onNext('Step2_PrivateKeyInput', { info });
67
71
}
68
72
},
69
-
onError(error) {
73
+
onError(err) {
70
74
let message: string | undefined;
71
75
72
-
if (error instanceof XRPCError) {
73
-
if (error.kind === 'InvalidRequest' && error.message.includes('resolve handle')) {
76
+
if (err instanceof ClientResponseError) {
77
+
if (err.error === 'InvalidRequest' && err.description?.includes('resolve handle')) {
74
78
message = `Can't seem to resolve handle, is it typed correctly?`;
75
79
}
76
-
} else if (error instanceof DidIsNotPlcError) {
77
-
message = error.message;
80
+
} else if (err instanceof DidIsNotPlcError) {
81
+
message = err.message;
78
82
}
79
83
80
84
if (message !== undefined) {
81
85
setError(message);
82
86
} else {
83
-
setError(`Something went wrong: ${error}`);
87
+
console.error(err);
88
+
89
+
setError(`Something went wrong: ${err}`);
84
90
}
85
91
},
86
92
});
···
101
107
placeholder="paul.bsky.social"
102
108
value={identifier()}
103
109
required
104
-
pattern={/* @once */ DID_OR_HANDLE_RE.source}
105
110
autofocus={isActive()}
106
111
onChange={setIdentifier}
107
112
/>
+18
-21
src/views/identity/plc-applicator/steps/step2_pds-authentication.tsx
+18
-21
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';
4
+
import { getPdsEndpoint } from '@atcute/identity';
5
5
6
-
import { getPdsEndpoint } from '~/api/types/did-doc';
7
-
import { TOTP_RE, formatTotpCode } from '~/api/utils/auth';
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
+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
+115
-69
src/views/identity/plc-applicator/steps/step3_operation-select.tsx
+115
-69
src/views/identity/plc-applicator/steps/step3_operation-select.tsx
···
1
-
import { createMemo, createSignal } from 'solid-js';
1
+
import { createMemo, createSignal, Show } from 'solid-js';
2
+
3
+
import {
4
+
type CompatibleOperation,
5
+
type DisputeCandidate,
6
+
getDisputeCandidates,
7
+
type IndexedEntryWithSigner,
8
+
normalizeOp,
9
+
} from '@atcute/did-plc';
10
+
import type { Did } from '@atcute/lexicons';
2
11
3
12
import Button from '~/components/inputs/button';
13
+
import RadioInput from '~/components/inputs/radio-input';
4
14
import SelectInput from '~/components/inputs/select-input';
5
-
import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard';
15
+
import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard';
6
16
7
-
import { PlcApplicatorConstraints } from '../page';
8
-
import { getCurrentSignersFromEntry } from '../plc-utils';
17
+
import type { PlcApplicatorConstraints } from '../page';
9
18
10
19
const Step3_OperationSelect = ({
11
20
data,
···
14
23
onNext,
15
24
}: WizardStepProps<PlcApplicatorConstraints, 'Step3_OperationSelect'>) => {
16
25
const [error, setError] = createSignal<string>();
17
-
const [selectedCid, setSelectedCid] = createSignal<string>();
18
26
19
-
const options = createMemo(() => {
20
-
const signingMethod = data.method;
21
-
const logs = data.info.logs;
22
-
23
-
let ownKey: string | undefined;
24
-
if (signingMethod.type === 'pds') {
25
-
ownKey = signingMethod.recommendedDidDoc.rotationKeys?.at(-1);
26
-
} else if (signingMethod.type === 'private_key') {
27
-
ownKey = signingMethod.didPublicKey;
28
-
}
29
-
30
-
if (ownKey === undefined) {
31
-
return [];
32
-
}
27
+
const [type, setType] = createSignal<'append' | 'dispute'>();
28
+
const [cid, setCid] = createSignal<string>();
33
29
34
-
const length = logs.length;
35
-
const items = logs.map((entry, idx) => {
36
-
const signers = getCurrentSignersFromEntry(entry);
37
-
const last = idx === length - 1;
38
-
39
-
let enabled = signers.includes(ownKey!);
40
-
41
-
// If we're showing older operations for forking/nullification,
42
-
// check to see that our key has priority over the signer.
43
-
if (enabled && !last) {
44
-
if (signingMethod.type === 'pds') {
45
-
// `signPlcOperation` will always grab the last op
46
-
enabled = false;
47
-
} else {
48
-
const holderKey = logs[idx + 1].signedBy;
30
+
const canAppend = createMemo(() => {
31
+
const signing = data.method;
49
32
50
-
const holderPriority = signers.indexOf(holderKey);
51
-
const ownPriority = signers.indexOf(ownKey);
33
+
const lastOp = data.info.logs.at(-1) as IndexedEntryWithSigner<CompatibleOperation>;
34
+
const { rotationKeys } = normalizeOp(lastOp.operation);
52
35
53
-
enabled = ownPriority < holderPriority;
36
+
switch (signing.type) {
37
+
case 'pds': {
38
+
const key = signing.recommendedDidDoc.rotationKeys?.at(-1) as Did<'key'> | undefined;
39
+
if (!key) {
40
+
return false;
54
41
}
42
+
43
+
return rotationKeys.includes(key);
55
44
}
45
+
case 'private_key': {
46
+
return rotationKeys.includes(signing.didPublicKey);
47
+
}
48
+
}
49
+
});
56
50
57
-
return {
58
-
value: entry.cid,
59
-
label: `${entry.createdAt} (by ${entry.signedBy})`,
60
-
disabled: !enabled,
61
-
};
62
-
});
51
+
const disputes = createMemo((): DisputeCandidate[] => {
52
+
const signing = data.method;
63
53
64
-
return items.reverse();
54
+
switch (signing.type) {
55
+
case 'pds': {
56
+
// signPlcOperation always grabs the last operation, so we can't make
57
+
// any dispute attempts.
58
+
return [];
59
+
}
60
+
case 'private_key': {
61
+
return getDisputeCandidates(data.info.logs, signing.didPublicKey);
62
+
}
63
+
}
65
64
});
66
65
67
66
return (
68
67
<Stage
69
-
title="Select which operation to use as foundation"
68
+
title="What do you want to do?"
70
69
onSubmit={() => {
71
70
setError();
72
71
73
-
const cid = selectedCid();
74
-
const entry = data.info.logs.find((entry) => entry.cid === cid);
72
+
const $type = type();
73
+
const $cid = cid();
75
74
76
-
if (!entry) {
77
-
setError(`Can't find CID ${cid}`);
78
-
return;
79
-
}
75
+
switch ($type) {
76
+
case 'append': {
77
+
const lastOp = data.info.logs.at(-1) as IndexedEntryWithSigner<CompatibleOperation>;
80
78
81
-
const operation = entry.operation;
82
-
if (operation.type !== 'plc_operation' && operation.type === 'create') {
83
-
setError(`Expected operation to be of type "plc_operation" or "create"`);
84
-
return;
85
-
}
79
+
onNext('Step4_PayloadInput', {
80
+
info: data.info,
81
+
method: data.method,
82
+
base: lastOp,
83
+
});
86
84
87
-
onNext('Step4_PayloadInput', {
88
-
info: data.info,
89
-
method: data.method,
90
-
base: entry,
91
-
});
85
+
break;
86
+
}
87
+
case 'dispute': {
88
+
const entry = disputes().find((entry) => entry.base.cid === $cid);
89
+
if (!entry) {
90
+
setError(`Can't find dispute entry for ${$cid}`);
91
+
return;
92
+
}
93
+
94
+
onNext('Step4_PayloadInput', {
95
+
info: data.info,
96
+
method: data.method,
97
+
base: entry.base,
98
+
});
99
+
100
+
break;
101
+
}
102
+
}
92
103
}}
93
104
>
94
-
<SelectInput
95
-
label="Base operation"
96
-
blurb="Some operations can't be used as a base if the rotation key does not have the privilege for nullification, or if it is not listed."
105
+
<RadioInput
106
+
label="I want to..."
97
107
required
98
-
value={selectedCid()}
99
-
autofocus={isActive()}
100
-
options={[{ value: '', label: `Select an operation...` }, ...options()]}
101
-
onChange={setSelectedCid}
108
+
value={type()}
109
+
options={[
110
+
{
111
+
value: 'append',
112
+
label: `Append an operation`,
113
+
disabled: !canAppend(),
114
+
},
115
+
{
116
+
value: 'dispute',
117
+
label: `Dispute an existing operation`,
118
+
disabled: disputes().length === 0,
119
+
},
120
+
]}
121
+
onChange={setType}
102
122
/>
103
123
124
+
<Show when={type() === 'dispute'}>
125
+
<SelectInput
126
+
label="Dispute operation"
127
+
blurb="Select an operation to dispute."
128
+
required
129
+
value={cid()}
130
+
autofocus={isActive()}
131
+
options={[
132
+
{ value: '', label: `Select an operation...` },
133
+
...disputes().map((entry) => ({
134
+
value: entry.base.cid,
135
+
label: `${entry.base.cid} โ ${entry.disputed.cid} (by ${entry.disputed.signedBy})`,
136
+
})),
137
+
]}
138
+
onChange={setCid}
139
+
/>
140
+
</Show>
141
+
142
+
<Show when={!canAppend() && disputes().length === 0}>
143
+
<p class="whitespace-pre-wrap text-[0.8125rem] font-medium leading-5 text-red-800">
144
+
This rotation key can't be used.
145
+
</p>
146
+
</Show>
147
+
104
148
<StageErrorView error={error()} />
105
149
106
150
<StageActions hidden={!isActive()}>
···
108
152
<Button variant="secondary" onClick={onPrevious}>
109
153
Previous
110
154
</Button>
111
-
<Button type="submit">Next</Button>
155
+
<Button type="submit" disabled={type() === undefined}>
156
+
Next
157
+
</Button>
112
158
</StageActions>
113
159
</Stage>
114
160
);
+2
-2
src/views/identity/plc-applicator/steps/step4_payload-input.tsx
+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
+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
});
+9
-11
src/views/identity/plc-applicator/steps/step5_private-key-confirmation.tsx
+9
-11
src/views/identity/plc-applicator/steps/step5_private-key-confirmation.tsx
···
1
1
import { createSignal } from 'solid-js';
2
2
3
3
import * as CBOR from '@atcute/cbor';
4
+
import type { Operation, UnsignedOperation } from '@atcute/did-plc';
4
5
import { toBase64Url } from '@atcute/multibase';
5
-
6
-
import { PlcUpdateOp } from '~/api/types/plc';
7
6
8
7
import { generateConfirmationCode } from '~/lib/utils/confirmation-code';
9
8
import { createMutation } from '~/lib/utils/mutation';
10
9
11
10
import Button from '~/components/inputs/button';
12
11
import TextInput from '~/components/inputs/text-input';
13
-
import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard';
12
+
import { Stage, StageActions, StageErrorView, type WizardStepProps } from '~/components/wizard';
14
13
15
-
import { PlcApplicatorConstraints } from '../page';
14
+
import type { PlcApplicatorConstraints } from '../page';
16
15
17
16
const Step5_PrivateKeyConfirmation = ({
18
17
data,
···
30
29
const payload = data.payload;
31
30
const prev = data.base;
32
31
33
-
const operation: Omit<PlcUpdateOp, 'sig'> = {
32
+
const operation: UnsignedOperation = {
34
33
type: 'plc_operation',
35
34
prev: prev!.cid,
36
35
···
45
44
46
45
const signature = toBase64Url(sigBytes);
47
46
48
-
const signedOperation: PlcUpdateOp = {
47
+
const signedOperation: Operation = {
49
48
...operation,
50
49
sig: signature,
51
50
};
···
79
78
}}
80
79
>
81
80
<p class="text-pretty">
82
-
To continue with this submission, type in the following code{' '}
83
-
<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.
84
83
</p>
85
84
86
85
<TextInput
87
-
label="Confirmation code"
86
+
label="Confirmation"
88
87
type="text"
89
88
autocomplete="one-time-code"
90
89
autocorrect="off"
91
90
required
92
91
pattern={code}
93
-
placeholder="AAAAA-BBBBB"
94
92
autofocus={isActive()}
95
93
monospace
96
94
/>
···
122
120
123
121
export default Step5_PrivateKeyConfirmation;
124
122
125
-
const pushPlcOperation = async (did: string, operation: PlcUpdateOp) => {
123
+
const pushPlcOperation = async (did: string, operation: Operation) => {
126
124
const origin = import.meta.env.VITE_PLC_DIRECTORY_URL;
127
125
const response = await fetch(`${origin}/${did}`, {
128
126
method: 'post',
+2
-2
src/views/identity/plc-applicator/steps/step6_finished.tsx
+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 (
+31
-32
src/views/identity/plc-oplogs.tsx
+31
-32
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
-
import { At } from '@atcute/client/lexicons';
3
+
import type { IndexedEntry, Service } from '@atcute/did-plc';
4
+
import { isPlcDid } from '@atcute/identity';
5
+
import { isHandle, type Did, type Handle } from '@atcute/lexicons/syntax';
4
6
5
7
import { resolveHandleViaAppView } from '~/api/queries/handle';
6
-
import { PlcLogEntry, Service } from '~/api/types/plc';
7
-
import { DID_OR_HANDLE_RE, isDid } 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({
···
29
30
const query = createQuery(
30
31
() => params.q,
31
32
async (identifier, signal) => {
32
-
let did: At.DID;
33
-
if (isDid(identifier)) {
33
+
let did: Did<'plc'>;
34
+
if (isPlcDid(identifier)) {
34
35
did = identifier;
35
-
} else {
36
-
did = await resolveHandleViaAppView({ handle: identifier, signal });
37
-
}
36
+
} else if (isHandle(identifier)) {
37
+
const resolved = await resolveHandleViaAppView({ handle: identifier, signal });
38
+
if (!isPlcDid(resolved)) {
39
+
throw new Error(`${identifier} is not a valid identifier`);
40
+
}
38
41
39
-
if (!did.startsWith('did:plc:')) {
40
-
throw new Error(`${did} is not plc`);
42
+
did = resolved;
43
+
} else {
44
+
throw new Error(`${identifier} is not a valid identifier`);
41
45
}
42
46
43
47
const logs = await getPlcAuditLogs({ did, signal });
···
52
56
53
57
return (
54
58
<>
55
-
<div class="p-4">
56
-
<h1 class="text-lg font-bold text-purple-800">View PLC operation logs</h1>
57
-
<p class="text-gray-600">Show history of a did:plc identity</p>
58
-
</div>
59
-
<hr class="mx-4 border-gray-300" />
59
+
<PageHeader title="View PLC operation logs" subtitle="Show history of a did:plc identity" />
60
60
61
61
<form
62
62
onSubmit={(ev) => {
63
63
const formData = new FormData(ev.currentTarget);
64
64
ev.preventDefault();
65
65
66
-
const ident = formData.get('ident') as string;
66
+
const ident = formData.get('ident') as Did | Handle;
67
67
setParams({ q: ident });
68
68
}}
69
69
class="m-4 flex flex-col gap-4"
···
73
73
type="text"
74
74
name="ident"
75
75
autocomplete="username"
76
-
pattern={/* @once */ DID_OR_HANDLE_RE.source}
77
76
placeholder="paul.bsky.social"
78
77
autofocus
79
78
/>
···
372
371
type DiffEntry =
373
372
| {
374
373
type: 'identity_created';
375
-
orig: PlcLogEntry;
374
+
orig: IndexedEntry;
376
375
nullified: boolean;
377
376
at: string;
378
377
rotationKeys: string[];
···
382
381
}
383
382
| {
384
383
type: 'identity_tombstoned';
385
-
orig: PlcLogEntry;
384
+
orig: IndexedEntry;
386
385
nullified: boolean;
387
386
at: string;
388
387
}
389
388
| {
390
389
type: 'rotation_key_added';
391
-
orig: PlcLogEntry;
390
+
orig: IndexedEntry;
392
391
nullified: boolean;
393
392
at: string;
394
393
rotation_key: string;
395
394
}
396
395
| {
397
396
type: 'rotation_key_removed';
398
-
orig: PlcLogEntry;
397
+
orig: IndexedEntry;
399
398
nullified: boolean;
400
399
at: string;
401
400
rotation_key: string;
402
401
}
403
402
| {
404
403
type: 'verification_method_added';
405
-
orig: PlcLogEntry;
404
+
orig: IndexedEntry;
406
405
nullified: boolean;
407
406
at: string;
408
407
method_id: string;
···
410
409
}
411
410
| {
412
411
type: 'verification_method_removed';
413
-
orig: PlcLogEntry;
412
+
orig: IndexedEntry;
414
413
nullified: boolean;
415
414
at: string;
416
415
method_id: string;
···
418
417
}
419
418
| {
420
419
type: 'verification_method_changed';
421
-
orig: PlcLogEntry;
420
+
orig: IndexedEntry;
422
421
nullified: boolean;
423
422
at: string;
424
423
method_id: string;
···
427
426
}
428
427
| {
429
428
type: 'handle_added';
430
-
orig: PlcLogEntry;
429
+
orig: IndexedEntry;
431
430
nullified: boolean;
432
431
at: string;
433
432
handle: string;
434
433
}
435
434
| {
436
435
type: 'handle_removed';
437
-
orig: PlcLogEntry;
436
+
orig: IndexedEntry;
438
437
nullified: boolean;
439
438
at: string;
440
439
handle: string;
441
440
}
442
441
| {
443
442
type: 'handle_changed';
444
-
orig: PlcLogEntry;
443
+
orig: IndexedEntry;
445
444
nullified: boolean;
446
445
at: string;
447
446
prev_handle: string;
···
449
448
}
450
449
| {
451
450
type: 'service_added';
452
-
orig: PlcLogEntry;
451
+
orig: IndexedEntry;
453
452
nullified: boolean;
454
453
at: string;
455
454
service_id: string;
···
458
457
}
459
458
| {
460
459
type: 'service_removed';
461
-
orig: PlcLogEntry;
460
+
orig: IndexedEntry;
462
461
nullified: boolean;
463
462
at: string;
464
463
service_id: string;
···
467
466
}
468
467
| {
469
468
type: 'service_changed';
470
-
orig: PlcLogEntry;
469
+
orig: IndexedEntry;
471
470
nullified: boolean;
472
471
at: string;
473
472
service_id: string;
···
477
476
next_service_endpoint: string;
478
477
};
479
478
480
-
const createOperationHistory = (entries: PlcLogEntry[]): DiffEntry[] => {
479
+
const createOperationHistory = (entries: IndexedEntry[]): DiffEntry[] => {
481
480
const history: DiffEntry[] = [];
482
481
483
482
for (let idx = 0, len = entries.length; idx < len; idx++) {
-209
src/views/repository/car-unpack.tsx
-209
src/views/repository/car-unpack.tsx
···
1
-
import { FileSystemWritableFileStream, showSaveFilePicker } from 'native-file-system-adapter';
2
-
import { createSignal } from 'solid-js';
3
-
4
-
import { iterateAtpRepo } from '@atcute/car';
5
-
import { writeTarEntry } from '@mary/tar';
6
-
7
-
import { createDropZone } from '~/lib/hooks/dropzone';
8
-
import { useTitle } from '~/lib/navigation/router';
9
-
import { makeAbortable } from '~/lib/utils/abortable';
10
-
11
-
import Logger, { createLogger } from '~/components/logger';
12
-
13
-
// @ts-expect-error: new API
14
-
const yieldToScheduler: () => Promise<void> = window?.scheduler?.yield
15
-
? // @ts-expect-error: whatever
16
-
window.scheduler.yield.bind(window.scheduler)
17
-
: undefined;
18
-
19
-
const yieldToIdle =
20
-
typeof requestIdleCallback === 'function'
21
-
? () => new Promise((resolve) => requestIdleCallback(resolve))
22
-
: () => new Promise((resolve) => setTimeout(resolve, 1));
23
-
24
-
const UnpackCarPage = () => {
25
-
const logger = createLogger();
26
-
27
-
const [getSignal, cleanup] = makeAbortable();
28
-
const [pending, setPending] = createSignal(false);
29
-
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
-
const mutate = async (file: File, signal: AbortSignal) => {
42
-
logger.info(`Starting extraction for ${file.name}`);
43
-
44
-
const buf = await file.arrayBuffer();
45
-
const ui8 = new Uint8Array(buf);
46
-
47
-
let currentCollection: string | undefined;
48
-
let count = 0;
49
-
50
-
let writable: FileSystemWritableFileStream | undefined;
51
-
52
-
for (const { collection, rkey, record } of iterateAtpRepo(ui8)) {
53
-
if (writable === undefined) {
54
-
using _progress = logger.progress(`Waiting for the user`);
55
-
56
-
const fd = await showSaveFilePicker({
57
-
suggestedName: `${file.name.replace(/\.car$/, '')}.tar`,
58
-
59
-
// @ts-expect-error: ponyfill doesn't have the full typings
60
-
id: 'car-unpack',
61
-
startIn: 'downloads',
62
-
types: [
63
-
{
64
-
description: 'Tarball archive',
65
-
accept: { 'application/tar': ['.tar'] },
66
-
},
67
-
],
68
-
}).catch((err) => {
69
-
console.warn(err);
70
-
71
-
if (err instanceof DOMException && err.name === 'AbortError') {
72
-
logger.warn(`Opened the file picker, but it was aborted`);
73
-
} else {
74
-
logger.warn(`Something went wrong when opening the file picker`);
75
-
}
76
-
77
-
return undefined;
78
-
});
79
-
80
-
writable = await fd?.createWritable();
81
-
82
-
if (writable === undefined) {
83
-
// We already handled the errors above
84
-
return;
85
-
}
86
-
}
87
-
88
-
signal.throwIfAborted();
89
-
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
-
const entry = writeTarEntry({
100
-
filename: `${collection}/${filenamify(rkey)}.json`,
101
-
data: JSON.stringify(record, null, 2),
102
-
});
103
-
104
-
writable.write(entry);
105
-
count++;
106
-
107
-
if (yieldToScheduler !== undefined) {
108
-
await yieldToScheduler();
109
-
}
110
-
}
111
-
112
-
signal.throwIfAborted();
113
-
114
-
if (writable === undefined) {
115
-
// If we got here it means the above loop never iterated
116
-
logger.log(`CAR file has no records`);
117
-
} else {
118
-
logger.log(`${count} records extracted`);
119
-
120
-
{
121
-
using _progress = logger.progress(`Flushing writes`);
122
-
await writable.close();
123
-
}
124
-
125
-
logger.log(`Finished!`);
126
-
}
127
-
};
128
-
129
-
const onFileDrop = (files: File[]) => {
130
-
if (pending() || files.length < 1) {
131
-
return;
132
-
}
133
-
134
-
const signal = getSignal();
135
-
136
-
setPending(true);
137
-
mutate(files[0], signal).then(
138
-
() => {
139
-
if (signal.aborted) {
140
-
return;
141
-
}
142
-
143
-
cleanup();
144
-
setPending(false);
145
-
},
146
-
(err) => {
147
-
if (signal.aborted) {
148
-
return;
149
-
}
150
-
151
-
cleanup();
152
-
setPending(false);
153
-
154
-
console.error(err);
155
-
logger.error(`Critical error: ${err}\nFile might be malformed, or might not be a CAR archive`);
156
-
},
157
-
);
158
-
};
159
-
160
-
useTitle(() => `Unpack CAR file โ boat`);
161
-
162
-
return (
163
-
<>
164
-
<div class="p-4">
165
-
<h1 class="text-lg font-bold text-purple-800">Unpack CAR file</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" />
169
-
170
-
<div class="p-4">
171
-
<fieldset
172
-
ref={dropRef}
173
-
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>
196
-
</div>
197
-
<hr class="mx-4 border-gray-300" />
198
-
199
-
<Logger logger={logger} />
200
-
</>
201
-
);
202
-
};
203
-
204
-
export default UnpackCarPage;
205
-
206
-
const INVALID_CHAR_RE = /[<>:"/\\|?*\x00-\x1F]/g;
207
-
const filenamify = (name: string) => {
208
-
return name.replace(INVALID_CHAR_RE, '~');
209
-
};
+65
src/views/repository/repo-archive-explore/page.tsx
+65
src/views/repository/repo-archive-explore/page.tsx
···
1
+
import { Match, Switch } from 'solid-js';
2
+
3
+
import { fromStream } from '@atcute/repo';
4
+
5
+
import { createMutation } from '~/lib/utils/mutation';
6
+
7
+
import type { Archive, RecordEntry } from './types';
8
+
import ExploreView from './views/explore';
9
+
import WelcomeView from './views/welcome';
10
+
11
+
const ArchiveExplorePage = () => {
12
+
const mutation = createMutation({
13
+
async mutationFn({ file }: { file: File }): Promise<Archive> {
14
+
const stream = file.stream();
15
+
await using repo = fromStream(stream);
16
+
17
+
const collections = new Map<string, RecordEntry[]>();
18
+
const archive: Archive = {
19
+
file: file,
20
+
entries: [],
21
+
};
22
+
23
+
for await (const entry of repo) {
24
+
let list = collections.get(entry.collection);
25
+
if (list === undefined) {
26
+
collections.set(entry.collection, (list = []));
27
+
archive.entries.push({
28
+
name: entry.collection,
29
+
entries: list,
30
+
});
31
+
}
32
+
33
+
const carEntry = entry.carEntry;
34
+
35
+
list.push({
36
+
key: entry.rkey,
37
+
cid: entry.cid.$link,
38
+
dataStart: carEntry.bytesStart,
39
+
dataEnd: carEntry.bytesEnd,
40
+
});
41
+
}
42
+
43
+
return archive;
44
+
},
45
+
onError(err) {
46
+
console.error(err);
47
+
},
48
+
});
49
+
50
+
return (
51
+
<>
52
+
<Switch>
53
+
<Match when={mutation.data} keyed>
54
+
{(archive) => <ExploreView archive={archive} onClose={mutation.reset} />}
55
+
</Match>
56
+
57
+
<Match when>
58
+
<WelcomeView mutation={mutation} />
59
+
</Match>
60
+
</Switch>
61
+
</>
62
+
);
63
+
};
64
+
65
+
export default ArchiveExplorePage;
+29
src/views/repository/repo-archive-explore/types.ts
+29
src/views/repository/repo-archive-explore/types.ts
···
1
+
export interface Archive {
2
+
/** Actual CAR file */
3
+
file: File;
4
+
/** Collections in the CAR file */
5
+
entries: CollectionEntry[];
6
+
}
7
+
8
+
export interface CollectionEntry {
9
+
/** Collection name, e.g. "app.bsky.feed.post" */
10
+
name: string;
11
+
/** Entries under this collection */
12
+
entries: RecordEntry[];
13
+
}
14
+
15
+
export interface RecordEntry {
16
+
/** Record key, e.g. "3ll3hjomcxka6" */
17
+
key: string;
18
+
/** Record digest, e.g. "bafyreieueqsjugefehodlh4o4idd7fzik3koxno7io7x4qu3q4wofsfjl4" */
19
+
cid: string;
20
+
/** Start position of the record in the CAR file */
21
+
dataStart: number;
22
+
/** End position of the record in the CAR file */
23
+
dataEnd: number;
24
+
}
25
+
26
+
export type View =
27
+
| { type: 'repo' }
28
+
| { type: 'collection'; collection: CollectionEntry }
29
+
| { type: 'record'; collection: CollectionEntry; record: RecordEntry };
+41
src/views/repository/repo-archive-explore/views/explore/collection.tsx
+41
src/views/repository/repo-archive-explore/views/explore/collection.tsx
···
1
+
import * as TID from '@atcute/tid';
2
+
3
+
import type { CollectionEntry, View } from '../../types';
4
+
5
+
interface CollectionSubviewProps {
6
+
collection: CollectionEntry;
7
+
onRoute: (view: View) => void;
8
+
}
9
+
10
+
const CollectionSubview = ({ collection, onRoute }: CollectionSubviewProps) => {
11
+
return (
12
+
<div class="px-2 pb-4 pt-0">
13
+
<ul>
14
+
{collection.entries.map((entry) => {
15
+
const isTid = TID.validate(entry.key);
16
+
17
+
return (
18
+
<li class="flex items-center justify-between gap-1">
19
+
<button
20
+
onClick={() => {
21
+
onRoute({ type: 'record', collection, record: entry });
22
+
}}
23
+
class="flex min-w-0 flex-wrap items-center gap-0.5 rounded p-1.5 font-mono hover:bg-gray-200"
24
+
>
25
+
<span class="truncate font-medium text-purple-700">{/* @once */ entry.key}</span>
26
+
</button>
27
+
28
+
{isTid && (
29
+
<span class="p-1.5 font-mono text-xs text-gray-600">
30
+
{/* @once */ new Date(TID.parse(entry.key).timestamp / 1_000).toISOString()}
31
+
</span>
32
+
)}
33
+
</li>
34
+
);
35
+
})}
36
+
</ul>
37
+
</div>
38
+
);
39
+
};
40
+
41
+
export default CollectionSubview;
+114
src/views/repository/repo-archive-explore/views/explore/record.tsx
+114
src/views/repository/repo-archive-explore/views/explore/record.tsx
···
1
+
import { Match, Switch } from 'solid-js';
2
+
3
+
import * as CBOR from '@atcute/cbor';
4
+
5
+
import { createQuery } from '~/lib/utils/query';
6
+
7
+
import type { Archive, RecordEntry } from '../../types';
8
+
9
+
interface RecordSubviewProps {
10
+
archive: Archive;
11
+
record: RecordEntry;
12
+
}
13
+
14
+
const RecordSubview = ({ archive, record }: RecordSubviewProps) => {
15
+
const query = createQuery(
16
+
() => record,
17
+
async (_record, signal) => {
18
+
const stream = archive.file.stream();
19
+
20
+
const raw = await readStreamRange(stream, record.dataStart, record.dataEnd, signal);
21
+
const decoded = CBOR.decode(raw);
22
+
23
+
return { raw, decoded };
24
+
},
25
+
);
26
+
27
+
return (
28
+
<Switch>
29
+
<Match when={query.data} keyed>
30
+
{({ decoded }) => {
31
+
return (
32
+
<div class="flex grow flex-col">
33
+
<textarea
34
+
readonly
35
+
value={JSON.stringify(decoded, null, 2)}
36
+
class="grow resize-none border-0 px-4 pb-4 pt-2 font-mono text-sm"
37
+
/>
38
+
</div>
39
+
);
40
+
}}
41
+
</Match>
42
+
</Switch>
43
+
);
44
+
};
45
+
46
+
export default RecordSubview;
47
+
48
+
const readStreamRange = async (
49
+
stream: ReadableStream<Uint8Array>,
50
+
start: number,
51
+
end: number,
52
+
signal?: AbortSignal,
53
+
): Promise<Uint8Array> => {
54
+
if (start < 0) {
55
+
throw new RangeError(`invalid start position: ${start}`);
56
+
}
57
+
if (end <= start) {
58
+
throw new RangeError(`invalid end position: ${end}`);
59
+
}
60
+
61
+
const length = end - start;
62
+
const result = new Uint8Array(length);
63
+
64
+
let read = 0;
65
+
let written = 0;
66
+
67
+
for await (const chunk of createStreamIterator(stream)) {
68
+
signal?.throwIfAborted();
69
+
70
+
if (read + chunk.length <= start) {
71
+
read += chunk.length;
72
+
continue;
73
+
}
74
+
75
+
const offset = Math.max(0, start - read);
76
+
const toRead = Math.min(chunk.length - offset, length - written);
77
+
78
+
result.set(chunk.subarray(offset, offset + toRead), written);
79
+
80
+
written += toRead;
81
+
read += chunk.length;
82
+
83
+
if (written >= length) {
84
+
break;
85
+
}
86
+
}
87
+
88
+
return result;
89
+
};
90
+
91
+
const createStreamIterator: <T>(stream: ReadableStream<T>) => AsyncIterableIterator<T> =
92
+
Symbol.asyncIterator in ReadableStream.prototype
93
+
? // @ts-expect-error
94
+
(stream) => stream[Symbol.asyncIterator]()
95
+
: (stream) => {
96
+
const reader = stream.getReader();
97
+
98
+
return {
99
+
[Symbol.asyncIterator]() {
100
+
return this;
101
+
},
102
+
next() {
103
+
return reader.read() as Promise<IteratorResult<any>>;
104
+
},
105
+
async return() {
106
+
await reader.cancel();
107
+
return { done: true, value: undefined };
108
+
},
109
+
async throw(error: unknown) {
110
+
await reader.cancel(error);
111
+
return { done: true, value: undefined };
112
+
},
113
+
};
114
+
};
+50
src/views/repository/repo-archive-explore/views/explore/repo.tsx
+50
src/views/repository/repo-archive-explore/views/explore/repo.tsx
···
1
+
import ChevronRightIcon from '~/components/ic-icons/baseline-chevron-right';
2
+
3
+
import type { Archive, View } from '../../types';
4
+
5
+
interface RepoSubviewProps {
6
+
archive: Archive;
7
+
onRoute: (view: View) => void;
8
+
}
9
+
10
+
const RepoSubview = ({ archive, onRoute }: RepoSubviewProps) => {
11
+
return (
12
+
<div class="px-2 pb-4 pt-0">
13
+
<ul>
14
+
{archive.entries.map((entry) => {
15
+
const hasSingleEntry = entry.entries.length === 1;
16
+
17
+
return (
18
+
<li>
19
+
<button
20
+
onClick={() => {
21
+
if (hasSingleEntry) {
22
+
onRoute({ type: 'record', collection: entry, record: entry.entries[0] });
23
+
} else {
24
+
onRoute({ type: 'collection', collection: entry });
25
+
}
26
+
}}
27
+
class="flex max-w-full flex-wrap items-center gap-0.5 rounded p-1.5 font-mono hover:bg-gray-200"
28
+
>
29
+
<span
30
+
class={`truncate font-medium` + (hasSingleEntry ? ` text-gray-700` : ` text-purple-700`)}
31
+
>
32
+
{/* @once */ entry.name}
33
+
</span>
34
+
35
+
{hasSingleEntry && (
36
+
<>
37
+
<ChevronRightIcon class="shrink-0 text-base text-gray-500" />
38
+
<span class="truncate font-medium text-purple-700">{entry.entries[0].key}</span>
39
+
</>
40
+
)}
41
+
</button>
42
+
</li>
43
+
);
44
+
})}
45
+
</ul>
46
+
</div>
47
+
);
48
+
};
49
+
50
+
export default RepoSubview;
+134
src/views/repository/repo-archive-explore/views/explore.tsx
+134
src/views/repository/repo-archive-explore/views/explore.tsx
···
1
+
import { createSignal, Match, Show, Switch } from 'solid-js';
2
+
3
+
import ChevronRightIcon from '~/components/ic-icons/baseline-chevron-right';
4
+
import ArchiveOutlinedIcon from '~/components/ic-icons/outline-archive';
5
+
6
+
import type { Archive, View } from '../types';
7
+
8
+
import CloseIcon from '~/components/ic-icons/baseline-close';
9
+
import CollectionSubview from './explore/collection';
10
+
import RecordSubview from './explore/record';
11
+
import RepoSubview from './explore/repo';
12
+
13
+
export interface ExploreViewProps {
14
+
archive: Archive;
15
+
onClose: () => void;
16
+
}
17
+
18
+
const ExploreView = ({ archive, onClose }: ExploreViewProps) => {
19
+
const [view, setView] = createSignal<View>({ type: 'repo' });
20
+
21
+
return (
22
+
<>
23
+
<div class="sticky top-0 z-10 flex items-start justify-between gap-1 bg-white p-2">
24
+
<div class="flex flex-wrap items-center">
25
+
<button
26
+
type="button"
27
+
title="This repository"
28
+
disabled={view().type === 'repo'}
29
+
onClick={() => {
30
+
setView({ type: 'repo' });
31
+
}}
32
+
class="grid shrink-0 place-items-center rounded p-1.5 text-xl text-purple-700 hover:bg-gray-200 disabled:pointer-events-none disabled:text-black"
33
+
>
34
+
<ArchiveOutlinedIcon />
35
+
</button>
36
+
37
+
<Show
38
+
when={(() => {
39
+
const $view = view();
40
+
switch ($view.type) {
41
+
case 'collection':
42
+
case 'record': {
43
+
return $view.collection;
44
+
}
45
+
}
46
+
})()}
47
+
>
48
+
{(collection) => (
49
+
<>
50
+
<ChevronRightIcon class="shrink-0 text-base text-gray-500" />
51
+
<button
52
+
type="button"
53
+
disabled={view().type === 'collection'}
54
+
onClick={() => {
55
+
setView({ type: 'collection', collection: collection() });
56
+
}}
57
+
class="truncate rounded p-1.5 font-mono font-medium text-purple-700 hover:bg-gray-200 disabled:pointer-events-none disabled:text-black"
58
+
>
59
+
{collection().name}
60
+
</button>
61
+
</>
62
+
)}
63
+
</Show>
64
+
65
+
<Show
66
+
when={(() => {
67
+
const $view = view();
68
+
switch ($view.type) {
69
+
case 'record': {
70
+
return $view.record;
71
+
}
72
+
}
73
+
})()}
74
+
>
75
+
{(record) => (
76
+
<>
77
+
<ChevronRightIcon class="shrink-0 text-base text-gray-500" />
78
+
<button
79
+
type="button"
80
+
disabled={view().type === 'record'}
81
+
class="truncate rounded p-1.5 font-mono font-medium text-purple-700 hover:bg-gray-200 disabled:pointer-events-none disabled:text-black"
82
+
>
83
+
{record().key}
84
+
</button>
85
+
</>
86
+
)}
87
+
</Show>
88
+
</div>
89
+
90
+
<div class="grow"></div>
91
+
92
+
<button
93
+
type="button"
94
+
onClick={onClose}
95
+
class="grid shrink-0 place-items-center rounded p-1.5 text-xl hover:bg-gray-200"
96
+
>
97
+
<CloseIcon />
98
+
</button>
99
+
</div>
100
+
101
+
<Switch>
102
+
<Match when={view().type === 'repo'}>
103
+
<RepoSubview archive={archive} onRoute={setView} />
104
+
</Match>
105
+
106
+
<Match
107
+
when={(() => {
108
+
const $view = view();
109
+
if ($view.type === 'collection') {
110
+
return $view;
111
+
}
112
+
})()}
113
+
keyed
114
+
>
115
+
{({ collection }) => <CollectionSubview collection={collection} onRoute={setView} />}
116
+
</Match>
117
+
118
+
<Match
119
+
when={(() => {
120
+
const $view = view();
121
+
if ($view.type === 'record') {
122
+
return $view;
123
+
}
124
+
})()}
125
+
keyed
126
+
>
127
+
{({ record }) => <RecordSubview archive={archive} record={record} />}
128
+
</Match>
129
+
</Switch>
130
+
</>
131
+
);
132
+
};
133
+
134
+
export default ExploreView;
+45
src/views/repository/repo-archive-explore/views/welcome.tsx
+45
src/views/repository/repo-archive-explore/views/welcome.tsx
···
1
+
import { Show } from 'solid-js';
2
+
3
+
import type { MutationReturn } from '~/lib/utils/mutation';
4
+
5
+
import CircularProgress from '~/components/circular-progress';
6
+
import FileDropZone from '~/components/file-drop-zone';
7
+
import PageHeader from '~/components/page-header';
8
+
9
+
import type { Archive } from '../types';
10
+
11
+
interface WelcomeViewProps {
12
+
mutation: MutationReturn<Archive, { file: File }>;
13
+
}
14
+
15
+
const WelcomeView = ({ mutation }: WelcomeViewProps) => {
16
+
return (
17
+
<>
18
+
<PageHeader title="Explore archive" subtitle="Explore a repository archive" />
19
+
20
+
<div class="flex flex-col gap-4 p-4">
21
+
<FileDropZone
22
+
accept=".car,application/vnd.ipld.car"
23
+
dataTypes={['']}
24
+
onFiles={(files) => mutation.mutate({ file: files[0] })}
25
+
>
26
+
<div
27
+
hidden={!mutation.isPending}
28
+
class="absolute inset-0 flex flex-col items-center justify-center gap-3 bg-gray-50"
29
+
>
30
+
<CircularProgress />
31
+
<span class="font-medium">Reading CAR file</span>
32
+
</div>
33
+
</FileDropZone>
34
+
35
+
<Show when={mutation.error}>
36
+
<p class="whitespace-pre-wrap text-[0.8125rem] font-medium leading-5 text-red-800">
37
+
{'' + mutation.error}
38
+
</p>
39
+
</Show>
40
+
</div>
41
+
</>
42
+
);
43
+
};
44
+
45
+
export default WelcomeView;
+170
src/views/repository/repo-archive-unpack.tsx
+170
src/views/repository/repo-archive-unpack.tsx
···
1
+
import { FileSystemWritableFileStream, showSaveFilePicker } from 'native-file-system-adapter';
2
+
import { createSignal } from 'solid-js';
3
+
4
+
import { fromStream } from '@atcute/repo';
5
+
import { writeTarEntry } from '@mary/tar';
6
+
7
+
import { useTitle } from '~/lib/navigation/router';
8
+
import { makeAbortable } from '~/lib/utils/abortable';
9
+
10
+
import FileDropZone from '~/components/file-drop-zone';
11
+
import Logger, { createLogger } from '~/components/logger';
12
+
import PageHeader from '~/components/page-header';
13
+
14
+
// @ts-expect-error: new API
15
+
const yieldToScheduler: () => Promise<void> = window?.scheduler?.yield
16
+
? // @ts-expect-error: whatever
17
+
window.scheduler.yield.bind(window.scheduler)
18
+
: undefined;
19
+
20
+
const UnpackCarPage = () => {
21
+
const logger = createLogger();
22
+
23
+
const [getSignal, cleanup] = makeAbortable();
24
+
const [pending, setPending] = createSignal(false);
25
+
26
+
const mutate = async (file: File, signal: AbortSignal) => {
27
+
logger.log(`Starting extraction`);
28
+
29
+
const stream = file.stream();
30
+
await using repo = fromStream(stream);
31
+
32
+
let count = 0;
33
+
34
+
let writable: FileSystemWritableFileStream | undefined;
35
+
36
+
using progress = logger.progress(`Unpacking records (${count} entries)`);
37
+
38
+
for await (const { collection, rkey, record } of repo) {
39
+
if (writable === undefined) {
40
+
using _progress = logger.progress(`Waiting for the user`);
41
+
42
+
const fd = await showSaveFilePicker({
43
+
suggestedName: `${file.name.replace(/\.car$/, '')}.tar`,
44
+
45
+
// @ts-expect-error: ponyfill doesn't have the full typings
46
+
id: 'car-unpack',
47
+
startIn: 'downloads',
48
+
types: [
49
+
{
50
+
description: 'Tarball archive',
51
+
accept: { 'application/tar': ['.tar'] },
52
+
},
53
+
],
54
+
}).catch((err) => {
55
+
console.warn(err);
56
+
57
+
if (err instanceof DOMException && err.name === 'AbortError') {
58
+
logger.warn(`Opened the file picker, but it was aborted`);
59
+
} else {
60
+
logger.warn(`Something went wrong when opening the file picker`);
61
+
}
62
+
63
+
return undefined;
64
+
});
65
+
66
+
writable = await fd?.createWritable();
67
+
68
+
if (writable === undefined) {
69
+
// We already handled the errors above
70
+
return;
71
+
}
72
+
}
73
+
74
+
signal.throwIfAborted();
75
+
76
+
const entry = writeTarEntry({
77
+
filename: `${collection}/${filenamify(rkey)}.json`,
78
+
data: JSON.stringify(record, null, 2),
79
+
});
80
+
81
+
count++;
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
+
91
+
if (yieldToScheduler !== undefined) {
92
+
await yieldToScheduler();
93
+
}
94
+
}
95
+
96
+
signal.throwIfAborted();
97
+
98
+
if (writable === undefined) {
99
+
// If we got here it means the above loop never iterated
100
+
logger.log(`CAR file has no records`);
101
+
} else {
102
+
logger.log(`${count} records extracted`);
103
+
104
+
{
105
+
using _progress = logger.progress(`Flushing writes`);
106
+
await writable.close();
107
+
}
108
+
109
+
logger.log(`Finished!`);
110
+
}
111
+
};
112
+
113
+
const onFileDrop = (files: File[]) => {
114
+
if (pending() || files.length < 1) {
115
+
return;
116
+
}
117
+
118
+
const signal = getSignal();
119
+
120
+
setPending(true);
121
+
mutate(files[0], signal).then(
122
+
() => {
123
+
if (signal.aborted) {
124
+
return;
125
+
}
126
+
127
+
cleanup();
128
+
setPending(false);
129
+
},
130
+
(err) => {
131
+
if (signal.aborted) {
132
+
return;
133
+
}
134
+
135
+
cleanup();
136
+
setPending(false);
137
+
138
+
console.error(err);
139
+
logger.error(`Critical error: ${err}\nFile might be malformed, or might not be a CAR archive`);
140
+
},
141
+
);
142
+
};
143
+
144
+
useTitle(() => `Unpack archive โ boat`);
145
+
146
+
return (
147
+
<>
148
+
<PageHeader title="Unpack archive" subtitle="Extract a repository archive into a tarball" />
149
+
150
+
<div class="p-4">
151
+
<FileDropZone
152
+
accept=".car,application/vnd.ipld.car"
153
+
dataTypes={['']}
154
+
disabled={pending()}
155
+
onFiles={onFileDrop}
156
+
/>
157
+
</div>
158
+
<hr class="mx-4 border-gray-300" />
159
+
160
+
<Logger logger={logger} />
161
+
</>
162
+
);
163
+
};
164
+
165
+
export default UnpackCarPage;
166
+
167
+
const INVALID_CHAR_RE = /[<>:"/\\|?*\x00-\x1F]/g;
168
+
const filenamify = (name: string) => {
169
+
return name.replace(INVALID_CHAR_RE, '~');
170
+
};
+17
-35
src/views/repository/repo-export.tsx
+17
-35
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 { At } from '@atcute/client/lexicons';
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
-
import { getPdsEndpoint } from '~/api/types/did-doc';
9
9
import { isServiceUrlString } from '~/api/types/strings';
10
-
import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings';
11
10
12
11
import { useTitle } from '~/lib/navigation/router';
13
12
import { makeAbortable } from '~/lib/utils/abortable';
14
13
import { formatBytes } from '~/lib/utils/intl/bytes';
14
+
import { iterateStream } from '~/lib/utils/stream';
15
15
16
16
import Button from '~/components/inputs/button';
17
17
import TextInput from '~/components/inputs/text-input';
18
18
import Logger, { createLogger } from '~/components/logger';
19
+
import PageHeader from '~/components/page-header';
19
20
20
21
const RepoExportPage = () => {
21
22
const logger = createLogger();
···
34
35
}) => {
35
36
logger.info(`Starting export for ${identifier}`);
36
37
37
-
let did: At.DID;
38
-
if (isDid(identifier)) {
38
+
let did: AtprotoDid;
39
+
if (isAtprotoDid(identifier)) {
39
40
did = identifier;
40
-
} else if (service) {
41
-
did = await resolveHandleViaPds({ service, handle: identifier, signal });
42
-
logger.log(`Resolved handle to ${did}`);
41
+
} else if (isHandle(identifier)) {
42
+
if (service) {
43
+
did = await resolveHandleViaPds({ service, handle: identifier, signal });
44
+
logger.log(`Resolved handle to ${did}`);
45
+
} else {
46
+
did = await resolveHandleViaAppView({ handle: identifier, signal });
47
+
logger.log(`Resolved handle to ${did}`);
48
+
}
43
49
} else {
44
-
did = await resolveHandleViaAppView({ handle: identifier, signal });
45
-
logger.log(`Resolved handle to ${did}`);
50
+
logger.error(`Invalid identifier`);
51
+
return;
46
52
}
47
53
48
54
if (!service) {
···
131
137
132
138
return (
133
139
<>
134
-
<div class="p-4">
135
-
<h1 class="text-lg font-bold text-purple-800">Export repository</h1>
136
-
<p class="text-gray-600">Download an archive of an account's repository</p>
137
-
</div>
138
-
<hr class="mx-4 border-gray-300" />
140
+
<PageHeader title="Export repository" subtitle="Download an archive of an account's repository" />
139
141
140
142
<form
141
143
onSubmit={(ev) => {
···
186
188
type="text"
187
189
name="ident"
188
190
autocomplete="username"
189
-
pattern={/* @once */ DID_OR_HANDLE_RE.source}
190
191
placeholder="paul.bsky.social"
191
192
autofocus
192
193
/>
···
219
220
};
220
221
221
222
export default RepoExportPage;
222
-
223
-
export async function* iterateStream<T>(stream: ReadableStream<T>) {
224
-
// Get a lock on the stream
225
-
const reader = stream.getReader();
226
-
227
-
try {
228
-
while (true) {
229
-
const { done, value } = await reader.read();
230
-
231
-
if (done) {
232
-
return;
233
-
}
234
-
235
-
yield value;
236
-
}
237
-
} finally {
238
-
reader.releaseLock();
239
-
}
240
-
}
+4
-2
tsconfig.app.json
+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,