Daily Bluesky bot for AT Mot. Invites players and congratulates yesterday's solvers.
0

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'add/daily-bluesky-bot': daily Bluesky bot for AT Mot

+3619
+5
.gitignore
··· 1 + node_modules/ 2 + .wrangler/ 3 + dist/ 4 + .dev.vars 5 + *.log
+55
README.md
··· 1 + # atmot-bot 2 + 3 + Daily Bluesky bot for [AT Mot](https://atmot.herve.bzh). Posts once per language 4 + each day just after the UTC puzzle rollover: invites players to today's puzzle and 5 + congratulates yesterday's solvers (by count only). 6 + 7 + Runs as a stateless Cloudflare Worker with two Cron Triggers (EN 00:10 UTC, FR 8 + 00:11 UTC). Counts are read from the public Constellation backlink index; there is 9 + no database. The bot duplicates a small set of the app's frozen constants 10 + (`src/config.ts`) rather than importing the app. 11 + 12 + ## Develop 13 + 14 + ```sh 15 + npm install 16 + npm test # vitest — composer + facets + puzzle math 17 + npm run typecheck 18 + npm run dev # wrangler dev --test-scheduled (see Dry run below) 19 + ``` 20 + 21 + ## Dry run (no real post) 22 + 23 + Create a gitignored `.dev.vars`: 24 + 25 + ``` 26 + DRY_RUN = "1" 27 + ATMOT_BOT_IDENTIFIER = "atmot.herve.bzh" 28 + ATMOT_BOT_APP_PASSWORD = "dry-run-unused" 29 + ``` 30 + 31 + Then `npm run dev` and, in another shell: 32 + 33 + ```sh 34 + curl "http://localhost:8787/__scheduled?cron=10+0+*+*+*" # EN 35 + curl "http://localhost:8787/__scheduled?cron=11+0+*+*+*" # FR 36 + ``` 37 + 38 + The composed post is logged instead of published. 39 + 40 + ## Deploy 41 + 42 + The bot posts as **@atmot.herve.bzh** using a Bluesky **app password** (Settings → 43 + Privacy and security → App passwords — not the account password). 44 + 45 + ```sh 46 + npx wrangler login 47 + npx wrangler secret put ATMOT_BOT_IDENTIFIER # e.g. atmot.herve.bzh 48 + npx wrangler secret put ATMOT_BOT_APP_PASSWORD # the app password 49 + npm run deploy 50 + ``` 51 + 52 + The free Workers plan is sufficient: each language runs in its own scheduled 53 + invocation, so each gets the full 50-subrequest budget. Solver counting samples up 54 + to `SOLVER_SAMPLE_CAP` (20) records per language; beyond that the count is hedged 55 + (e.g. "20+"). On the paid plan, raise `SOLVER_SAMPLE_CAP` in `src/config.ts` to ~200.
+2917
package-lock.json
··· 1 + { 2 + "name": "atmot-bot", 3 + "version": "1.0.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "atmot-bot", 9 + "version": "1.0.0", 10 + "dependencies": { 11 + "@atcute/atproto": "^4.0.2", 12 + "@atcute/client": "^5.1.0", 13 + "@atcute/identity-resolver": "^2.0.0" 14 + }, 15 + "devDependencies": { 16 + "@cloudflare/workers-types": "^4.20240000.0", 17 + "typescript": "^6.0.3", 18 + "vitest": "^4.1.9", 19 + "wrangler": "^4.103.0" 20 + } 21 + }, 22 + "node_modules/@atcute/atproto": { 23 + "version": "4.0.2", 24 + "resolved": "https://registry.npmjs.org/@atcute/atproto/-/atproto-4.0.2.tgz", 25 + "integrity": "sha512-hLnvjiIOStpdUm0cEN+R5YydvbV0d6ap17Iv+t7i/nhSCN3TGMya7M0ftCWtCo+xoQ1EU6HK74R8jqXWlyrM0w==", 26 + "license": "0BSD", 27 + "dependencies": { 28 + "@atcute/lexicons": "^2.0.0" 29 + }, 30 + "peerDependencies": { 31 + "@atcute/lexicons": "^2.0.0" 32 + } 33 + }, 34 + "node_modules/@atcute/client": { 35 + "version": "5.1.0", 36 + "resolved": "https://registry.npmjs.org/@atcute/client/-/client-5.1.0.tgz", 37 + "integrity": "sha512-l2LYCY43QvrOsvS+q1d959x0yVeXQ5F7haloCB8MLzrTKT3s9fc4S3Kr+8JkgjPtdapgOPIeEdhWcrzP5WNLRg==", 38 + "license": "0BSD", 39 + "dependencies": { 40 + "@atcute/identity": "^2.0.0", 41 + "@atcute/lexicons": "^2.0.0" 42 + }, 43 + "peerDependencies": { 44 + "@atcute/lexicons": "^2.0.0" 45 + } 46 + }, 47 + "node_modules/@atcute/identity": { 48 + "version": "2.0.0", 49 + "resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-2.0.0.tgz", 50 + "integrity": "sha512-YXFsggO7eJYifqkN85+kUXJE2a1iI9AyuzPTDjtS/4WE1Zs1/XiPkWmwZlAgtp+pYhVtjm3mJqy/h/mZ0OnIVw==", 51 + "license": "0BSD", 52 + "dependencies": { 53 + "@atcute/lexicons": "^2.0.0", 54 + "valibot": "^1.4.0" 55 + }, 56 + "peerDependencies": { 57 + "@atcute/lexicons": "^2.0.0" 58 + } 59 + }, 60 + "node_modules/@atcute/identity-resolver": { 61 + "version": "2.0.0", 62 + "resolved": "https://registry.npmjs.org/@atcute/identity-resolver/-/identity-resolver-2.0.0.tgz", 63 + "integrity": "sha512-IKg1BDQAF2bIdN10DL6KAXmTjK+3enTU2IRbuani9TsFahBwGZ7O5FiVmTiL6QlGfauGNW5S0xNCOxWXWMoR2Q==", 64 + "license": "0BSD", 65 + "dependencies": { 66 + "@atcute/lexicons": "^2.0.0", 67 + "@atcute/util-fetch": "^2.0.0", 68 + "valibot": "^1.4.0" 69 + }, 70 + "peerDependencies": { 71 + "@atcute/identity": "^2.0.0", 72 + "@atcute/lexicons": "^2.0.0" 73 + } 74 + }, 75 + "node_modules/@atcute/lexicons": { 76 + "version": "2.0.1", 77 + "resolved": "https://registry.npmjs.org/@atcute/lexicons/-/lexicons-2.0.1.tgz", 78 + "integrity": "sha512-MsgGYgZ7bXcXWg/CzAQX+d+/u4YrelQYnxtX6NW3Bc+6eOyzxozBPRWVFzfsR9OCT10Qzqmuujul/8bRYNCZ6w==", 79 + "license": "0BSD", 80 + "dependencies": { 81 + "@atcute/uint8array": "^1.1.2", 82 + "@atcute/util-text": "^1.3.1", 83 + "@standard-schema/spec": "^1.1.0", 84 + "esm-env": "^1.2.2" 85 + } 86 + }, 87 + "node_modules/@atcute/uint8array": { 88 + "version": "1.1.2", 89 + "resolved": "https://registry.npmjs.org/@atcute/uint8array/-/uint8array-1.1.2.tgz", 90 + "integrity": "sha512-n+lutnbN9mKzSjSVdfsYfzJ40u2971H+iLSL46D6d7zcrA4delxusf/ftGFvj5oGW03OioaFgQOy3Lqa3JmTeA==", 91 + "license": "0BSD" 92 + }, 93 + "node_modules/@atcute/util-fetch": { 94 + "version": "2.0.0", 95 + "resolved": "https://registry.npmjs.org/@atcute/util-fetch/-/util-fetch-2.0.0.tgz", 96 + "integrity": "sha512-v+4aFQ/tuBqTV+URDJaFgm3mASWdglKXiPaGutJ1bs7QtQKmPZeesPY5MzW/a+MtI8GWCEJk8X9wOfalPOFSlg==", 97 + "license": "0BSD", 98 + "dependencies": { 99 + "valibot": "^1.4.0" 100 + } 101 + }, 102 + "node_modules/@atcute/util-text": { 103 + "version": "1.3.1", 104 + "resolved": "https://registry.npmjs.org/@atcute/util-text/-/util-text-1.3.1.tgz", 105 + "integrity": "sha512-MRgJXkx67znuBXuoAYCJkBZyd3OApL7zZlNf5kXhuoCXcdiu1nblRDycYTADSkym4epBSQWxh26kmI9sewaq6A==", 106 + "license": "0BSD", 107 + "dependencies": { 108 + "unicode-segmenter": "^0.14.5" 109 + } 110 + }, 111 + "node_modules/@cloudflare/kv-asset-handler": { 112 + "version": "0.5.0", 113 + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", 114 + "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==", 115 + "dev": true, 116 + "license": "MIT OR Apache-2.0", 117 + "engines": { 118 + "node": ">=22.0.0" 119 + } 120 + }, 121 + "node_modules/@cloudflare/unenv-preset": { 122 + "version": "2.16.1", 123 + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.1.tgz", 124 + "integrity": "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==", 125 + "dev": true, 126 + "license": "MIT OR Apache-2.0", 127 + "peerDependencies": { 128 + "unenv": "2.0.0-rc.24", 129 + "workerd": ">1.20260305.0 <2.0.0-0" 130 + }, 131 + "peerDependenciesMeta": { 132 + "workerd": { 133 + "optional": true 134 + } 135 + } 136 + }, 137 + "node_modules/@cloudflare/workerd-darwin-64": { 138 + "version": "1.20260623.1", 139 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260623.1.tgz", 140 + "integrity": "sha512-MvDoIsRTsUJRzAl1/4hDXL839piyyjCeYatBHWgMc12Go7nHxkgbRih+1GJImEiKACSentu410bOupcutqFbpg==", 141 + "cpu": [ 142 + "x64" 143 + ], 144 + "dev": true, 145 + "license": "Apache-2.0", 146 + "optional": true, 147 + "os": [ 148 + "darwin" 149 + ], 150 + "engines": { 151 + "node": ">=16" 152 + } 153 + }, 154 + "node_modules/@cloudflare/workerd-darwin-arm64": { 155 + "version": "1.20260623.1", 156 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260623.1.tgz", 157 + "integrity": "sha512-sNqHQvHPMOj5/BJOadtEZekRPSG5qQ0/ulC30ZRHRLnmx6tj5O4Wb3Nf0oznnI0pmjXhbv6b7+TOpDkaFMjbBg==", 158 + "cpu": [ 159 + "arm64" 160 + ], 161 + "dev": true, 162 + "license": "Apache-2.0", 163 + "optional": true, 164 + "os": [ 165 + "darwin" 166 + ], 167 + "engines": { 168 + "node": ">=16" 169 + } 170 + }, 171 + "node_modules/@cloudflare/workerd-linux-64": { 172 + "version": "1.20260623.1", 173 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260623.1.tgz", 174 + "integrity": "sha512-XYTWqTlZlCXqG+Po6awjXtlxw73hb3C39B/PP0sb4H9NI3V0eynq8Q7rXNe7DHJs2pWRfDJihQzpayQvpwf5wQ==", 175 + "cpu": [ 176 + "x64" 177 + ], 178 + "dev": true, 179 + "license": "Apache-2.0", 180 + "optional": true, 181 + "os": [ 182 + "linux" 183 + ], 184 + "engines": { 185 + "node": ">=16" 186 + } 187 + }, 188 + "node_modules/@cloudflare/workerd-linux-arm64": { 189 + "version": "1.20260623.1", 190 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260623.1.tgz", 191 + "integrity": "sha512-PC5UDKA8oQB3Gek/Y+ysovdHNjp55CihOQZd7F9xPwpkv9qTBB0mhyHnfoG2YHtW1bb9CNhuwiThaNxegpE4mg==", 192 + "cpu": [ 193 + "arm64" 194 + ], 195 + "dev": true, 196 + "license": "Apache-2.0", 197 + "optional": true, 198 + "os": [ 199 + "linux" 200 + ], 201 + "engines": { 202 + "node": ">=16" 203 + } 204 + }, 205 + "node_modules/@cloudflare/workerd-windows-64": { 206 + "version": "1.20260623.1", 207 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260623.1.tgz", 208 + "integrity": "sha512-OTHLCVYyN0pEfrajpjjnrGg5zA1GDnpNYmMz3x2ESFtH/oXRODsUQBllP7oJpJvMURF3rXSYwAhMojaftGry8w==", 209 + "cpu": [ 210 + "x64" 211 + ], 212 + "dev": true, 213 + "license": "Apache-2.0", 214 + "optional": true, 215 + "os": [ 216 + "win32" 217 + ], 218 + "engines": { 219 + "node": ">=16" 220 + } 221 + }, 222 + "node_modules/@cloudflare/workers-types": { 223 + "version": "4.20260625.1", 224 + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260625.1.tgz", 225 + "integrity": "sha512-asH0RhPHiNu/IUSssyiOJYAcGqysy0DJpO9fihC6KATaayD9CE1E9bgNQozTLUraxrCT2qkM4CBOIcV0M5NPJw==", 226 + "dev": true, 227 + "license": "MIT OR Apache-2.0" 228 + }, 229 + "node_modules/@cspotcode/source-map-support": { 230 + "version": "0.8.1", 231 + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 232 + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 233 + "dev": true, 234 + "license": "MIT", 235 + "dependencies": { 236 + "@jridgewell/trace-mapping": "0.3.9" 237 + }, 238 + "engines": { 239 + "node": ">=12" 240 + } 241 + }, 242 + "node_modules/@emnapi/core": { 243 + "version": "1.11.1", 244 + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", 245 + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", 246 + "dev": true, 247 + "license": "MIT", 248 + "optional": true, 249 + "dependencies": { 250 + "@emnapi/wasi-threads": "1.2.2", 251 + "tslib": "^2.4.0" 252 + } 253 + }, 254 + "node_modules/@emnapi/runtime": { 255 + "version": "1.11.1", 256 + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", 257 + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", 258 + "dev": true, 259 + "license": "MIT", 260 + "optional": true, 261 + "dependencies": { 262 + "tslib": "^2.4.0" 263 + } 264 + }, 265 + "node_modules/@emnapi/wasi-threads": { 266 + "version": "1.2.2", 267 + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", 268 + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", 269 + "dev": true, 270 + "license": "MIT", 271 + "optional": true, 272 + "dependencies": { 273 + "tslib": "^2.4.0" 274 + } 275 + }, 276 + "node_modules/@esbuild/aix-ppc64": { 277 + "version": "0.28.1", 278 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", 279 + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", 280 + "cpu": [ 281 + "ppc64" 282 + ], 283 + "dev": true, 284 + "license": "MIT", 285 + "optional": true, 286 + "os": [ 287 + "aix" 288 + ], 289 + "engines": { 290 + "node": ">=18" 291 + } 292 + }, 293 + "node_modules/@esbuild/android-arm": { 294 + "version": "0.28.1", 295 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", 296 + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", 297 + "cpu": [ 298 + "arm" 299 + ], 300 + "dev": true, 301 + "license": "MIT", 302 + "optional": true, 303 + "os": [ 304 + "android" 305 + ], 306 + "engines": { 307 + "node": ">=18" 308 + } 309 + }, 310 + "node_modules/@esbuild/android-arm64": { 311 + "version": "0.28.1", 312 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", 313 + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", 314 + "cpu": [ 315 + "arm64" 316 + ], 317 + "dev": true, 318 + "license": "MIT", 319 + "optional": true, 320 + "os": [ 321 + "android" 322 + ], 323 + "engines": { 324 + "node": ">=18" 325 + } 326 + }, 327 + "node_modules/@esbuild/android-x64": { 328 + "version": "0.28.1", 329 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", 330 + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", 331 + "cpu": [ 332 + "x64" 333 + ], 334 + "dev": true, 335 + "license": "MIT", 336 + "optional": true, 337 + "os": [ 338 + "android" 339 + ], 340 + "engines": { 341 + "node": ">=18" 342 + } 343 + }, 344 + "node_modules/@esbuild/darwin-arm64": { 345 + "version": "0.28.1", 346 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", 347 + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", 348 + "cpu": [ 349 + "arm64" 350 + ], 351 + "dev": true, 352 + "license": "MIT", 353 + "optional": true, 354 + "os": [ 355 + "darwin" 356 + ], 357 + "engines": { 358 + "node": ">=18" 359 + } 360 + }, 361 + "node_modules/@esbuild/darwin-x64": { 362 + "version": "0.28.1", 363 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", 364 + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", 365 + "cpu": [ 366 + "x64" 367 + ], 368 + "dev": true, 369 + "license": "MIT", 370 + "optional": true, 371 + "os": [ 372 + "darwin" 373 + ], 374 + "engines": { 375 + "node": ">=18" 376 + } 377 + }, 378 + "node_modules/@esbuild/freebsd-arm64": { 379 + "version": "0.28.1", 380 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", 381 + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", 382 + "cpu": [ 383 + "arm64" 384 + ], 385 + "dev": true, 386 + "license": "MIT", 387 + "optional": true, 388 + "os": [ 389 + "freebsd" 390 + ], 391 + "engines": { 392 + "node": ">=18" 393 + } 394 + }, 395 + "node_modules/@esbuild/freebsd-x64": { 396 + "version": "0.28.1", 397 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", 398 + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", 399 + "cpu": [ 400 + "x64" 401 + ], 402 + "dev": true, 403 + "license": "MIT", 404 + "optional": true, 405 + "os": [ 406 + "freebsd" 407 + ], 408 + "engines": { 409 + "node": ">=18" 410 + } 411 + }, 412 + "node_modules/@esbuild/linux-arm": { 413 + "version": "0.28.1", 414 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", 415 + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", 416 + "cpu": [ 417 + "arm" 418 + ], 419 + "dev": true, 420 + "license": "MIT", 421 + "optional": true, 422 + "os": [ 423 + "linux" 424 + ], 425 + "engines": { 426 + "node": ">=18" 427 + } 428 + }, 429 + "node_modules/@esbuild/linux-arm64": { 430 + "version": "0.28.1", 431 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", 432 + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", 433 + "cpu": [ 434 + "arm64" 435 + ], 436 + "dev": true, 437 + "license": "MIT", 438 + "optional": true, 439 + "os": [ 440 + "linux" 441 + ], 442 + "engines": { 443 + "node": ">=18" 444 + } 445 + }, 446 + "node_modules/@esbuild/linux-ia32": { 447 + "version": "0.28.1", 448 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", 449 + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", 450 + "cpu": [ 451 + "ia32" 452 + ], 453 + "dev": true, 454 + "license": "MIT", 455 + "optional": true, 456 + "os": [ 457 + "linux" 458 + ], 459 + "engines": { 460 + "node": ">=18" 461 + } 462 + }, 463 + "node_modules/@esbuild/linux-loong64": { 464 + "version": "0.28.1", 465 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", 466 + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", 467 + "cpu": [ 468 + "loong64" 469 + ], 470 + "dev": true, 471 + "license": "MIT", 472 + "optional": true, 473 + "os": [ 474 + "linux" 475 + ], 476 + "engines": { 477 + "node": ">=18" 478 + } 479 + }, 480 + "node_modules/@esbuild/linux-mips64el": { 481 + "version": "0.28.1", 482 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", 483 + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", 484 + "cpu": [ 485 + "mips64el" 486 + ], 487 + "dev": true, 488 + "license": "MIT", 489 + "optional": true, 490 + "os": [ 491 + "linux" 492 + ], 493 + "engines": { 494 + "node": ">=18" 495 + } 496 + }, 497 + "node_modules/@esbuild/linux-ppc64": { 498 + "version": "0.28.1", 499 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", 500 + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", 501 + "cpu": [ 502 + "ppc64" 503 + ], 504 + "dev": true, 505 + "license": "MIT", 506 + "optional": true, 507 + "os": [ 508 + "linux" 509 + ], 510 + "engines": { 511 + "node": ">=18" 512 + } 513 + }, 514 + "node_modules/@esbuild/linux-riscv64": { 515 + "version": "0.28.1", 516 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", 517 + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", 518 + "cpu": [ 519 + "riscv64" 520 + ], 521 + "dev": true, 522 + "license": "MIT", 523 + "optional": true, 524 + "os": [ 525 + "linux" 526 + ], 527 + "engines": { 528 + "node": ">=18" 529 + } 530 + }, 531 + "node_modules/@esbuild/linux-s390x": { 532 + "version": "0.28.1", 533 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", 534 + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", 535 + "cpu": [ 536 + "s390x" 537 + ], 538 + "dev": true, 539 + "license": "MIT", 540 + "optional": true, 541 + "os": [ 542 + "linux" 543 + ], 544 + "engines": { 545 + "node": ">=18" 546 + } 547 + }, 548 + "node_modules/@esbuild/linux-x64": { 549 + "version": "0.28.1", 550 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", 551 + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", 552 + "cpu": [ 553 + "x64" 554 + ], 555 + "dev": true, 556 + "license": "MIT", 557 + "optional": true, 558 + "os": [ 559 + "linux" 560 + ], 561 + "engines": { 562 + "node": ">=18" 563 + } 564 + }, 565 + "node_modules/@esbuild/netbsd-arm64": { 566 + "version": "0.28.1", 567 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", 568 + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", 569 + "cpu": [ 570 + "arm64" 571 + ], 572 + "dev": true, 573 + "license": "MIT", 574 + "optional": true, 575 + "os": [ 576 + "netbsd" 577 + ], 578 + "engines": { 579 + "node": ">=18" 580 + } 581 + }, 582 + "node_modules/@esbuild/netbsd-x64": { 583 + "version": "0.28.1", 584 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", 585 + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", 586 + "cpu": [ 587 + "x64" 588 + ], 589 + "dev": true, 590 + "license": "MIT", 591 + "optional": true, 592 + "os": [ 593 + "netbsd" 594 + ], 595 + "engines": { 596 + "node": ">=18" 597 + } 598 + }, 599 + "node_modules/@esbuild/openbsd-arm64": { 600 + "version": "0.28.1", 601 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", 602 + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", 603 + "cpu": [ 604 + "arm64" 605 + ], 606 + "dev": true, 607 + "license": "MIT", 608 + "optional": true, 609 + "os": [ 610 + "openbsd" 611 + ], 612 + "engines": { 613 + "node": ">=18" 614 + } 615 + }, 616 + "node_modules/@esbuild/openbsd-x64": { 617 + "version": "0.28.1", 618 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", 619 + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", 620 + "cpu": [ 621 + "x64" 622 + ], 623 + "dev": true, 624 + "license": "MIT", 625 + "optional": true, 626 + "os": [ 627 + "openbsd" 628 + ], 629 + "engines": { 630 + "node": ">=18" 631 + } 632 + }, 633 + "node_modules/@esbuild/openharmony-arm64": { 634 + "version": "0.28.1", 635 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", 636 + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", 637 + "cpu": [ 638 + "arm64" 639 + ], 640 + "dev": true, 641 + "license": "MIT", 642 + "optional": true, 643 + "os": [ 644 + "openharmony" 645 + ], 646 + "engines": { 647 + "node": ">=18" 648 + } 649 + }, 650 + "node_modules/@esbuild/sunos-x64": { 651 + "version": "0.28.1", 652 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", 653 + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", 654 + "cpu": [ 655 + "x64" 656 + ], 657 + "dev": true, 658 + "license": "MIT", 659 + "optional": true, 660 + "os": [ 661 + "sunos" 662 + ], 663 + "engines": { 664 + "node": ">=18" 665 + } 666 + }, 667 + "node_modules/@esbuild/win32-arm64": { 668 + "version": "0.28.1", 669 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", 670 + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", 671 + "cpu": [ 672 + "arm64" 673 + ], 674 + "dev": true, 675 + "license": "MIT", 676 + "optional": true, 677 + "os": [ 678 + "win32" 679 + ], 680 + "engines": { 681 + "node": ">=18" 682 + } 683 + }, 684 + "node_modules/@esbuild/win32-ia32": { 685 + "version": "0.28.1", 686 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", 687 + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", 688 + "cpu": [ 689 + "ia32" 690 + ], 691 + "dev": true, 692 + "license": "MIT", 693 + "optional": true, 694 + "os": [ 695 + "win32" 696 + ], 697 + "engines": { 698 + "node": ">=18" 699 + } 700 + }, 701 + "node_modules/@esbuild/win32-x64": { 702 + "version": "0.28.1", 703 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", 704 + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", 705 + "cpu": [ 706 + "x64" 707 + ], 708 + "dev": true, 709 + "license": "MIT", 710 + "optional": true, 711 + "os": [ 712 + "win32" 713 + ], 714 + "engines": { 715 + "node": ">=18" 716 + } 717 + }, 718 + "node_modules/@img/colour": { 719 + "version": "1.1.0", 720 + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", 721 + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", 722 + "dev": true, 723 + "license": "MIT", 724 + "engines": { 725 + "node": ">=18" 726 + } 727 + }, 728 + "node_modules/@img/sharp-darwin-arm64": { 729 + "version": "0.34.5", 730 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", 731 + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", 732 + "cpu": [ 733 + "arm64" 734 + ], 735 + "dev": true, 736 + "license": "Apache-2.0", 737 + "optional": true, 738 + "os": [ 739 + "darwin" 740 + ], 741 + "engines": { 742 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 743 + }, 744 + "funding": { 745 + "url": "https://opencollective.com/libvips" 746 + }, 747 + "optionalDependencies": { 748 + "@img/sharp-libvips-darwin-arm64": "1.2.4" 749 + } 750 + }, 751 + "node_modules/@img/sharp-darwin-x64": { 752 + "version": "0.34.5", 753 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", 754 + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", 755 + "cpu": [ 756 + "x64" 757 + ], 758 + "dev": true, 759 + "license": "Apache-2.0", 760 + "optional": true, 761 + "os": [ 762 + "darwin" 763 + ], 764 + "engines": { 765 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 766 + }, 767 + "funding": { 768 + "url": "https://opencollective.com/libvips" 769 + }, 770 + "optionalDependencies": { 771 + "@img/sharp-libvips-darwin-x64": "1.2.4" 772 + } 773 + }, 774 + "node_modules/@img/sharp-libvips-darwin-arm64": { 775 + "version": "1.2.4", 776 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", 777 + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", 778 + "cpu": [ 779 + "arm64" 780 + ], 781 + "dev": true, 782 + "license": "LGPL-3.0-or-later", 783 + "optional": true, 784 + "os": [ 785 + "darwin" 786 + ], 787 + "funding": { 788 + "url": "https://opencollective.com/libvips" 789 + } 790 + }, 791 + "node_modules/@img/sharp-libvips-darwin-x64": { 792 + "version": "1.2.4", 793 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", 794 + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", 795 + "cpu": [ 796 + "x64" 797 + ], 798 + "dev": true, 799 + "license": "LGPL-3.0-or-later", 800 + "optional": true, 801 + "os": [ 802 + "darwin" 803 + ], 804 + "funding": { 805 + "url": "https://opencollective.com/libvips" 806 + } 807 + }, 808 + "node_modules/@img/sharp-libvips-linux-arm": { 809 + "version": "1.2.4", 810 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", 811 + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", 812 + "cpu": [ 813 + "arm" 814 + ], 815 + "dev": true, 816 + "libc": [ 817 + "glibc" 818 + ], 819 + "license": "LGPL-3.0-or-later", 820 + "optional": true, 821 + "os": [ 822 + "linux" 823 + ], 824 + "funding": { 825 + "url": "https://opencollective.com/libvips" 826 + } 827 + }, 828 + "node_modules/@img/sharp-libvips-linux-arm64": { 829 + "version": "1.2.4", 830 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", 831 + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", 832 + "cpu": [ 833 + "arm64" 834 + ], 835 + "dev": true, 836 + "libc": [ 837 + "glibc" 838 + ], 839 + "license": "LGPL-3.0-or-later", 840 + "optional": true, 841 + "os": [ 842 + "linux" 843 + ], 844 + "funding": { 845 + "url": "https://opencollective.com/libvips" 846 + } 847 + }, 848 + "node_modules/@img/sharp-libvips-linux-ppc64": { 849 + "version": "1.2.4", 850 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", 851 + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", 852 + "cpu": [ 853 + "ppc64" 854 + ], 855 + "dev": true, 856 + "libc": [ 857 + "glibc" 858 + ], 859 + "license": "LGPL-3.0-or-later", 860 + "optional": true, 861 + "os": [ 862 + "linux" 863 + ], 864 + "funding": { 865 + "url": "https://opencollective.com/libvips" 866 + } 867 + }, 868 + "node_modules/@img/sharp-libvips-linux-riscv64": { 869 + "version": "1.2.4", 870 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", 871 + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", 872 + "cpu": [ 873 + "riscv64" 874 + ], 875 + "dev": true, 876 + "libc": [ 877 + "glibc" 878 + ], 879 + "license": "LGPL-3.0-or-later", 880 + "optional": true, 881 + "os": [ 882 + "linux" 883 + ], 884 + "funding": { 885 + "url": "https://opencollective.com/libvips" 886 + } 887 + }, 888 + "node_modules/@img/sharp-libvips-linux-s390x": { 889 + "version": "1.2.4", 890 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", 891 + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", 892 + "cpu": [ 893 + "s390x" 894 + ], 895 + "dev": true, 896 + "libc": [ 897 + "glibc" 898 + ], 899 + "license": "LGPL-3.0-or-later", 900 + "optional": true, 901 + "os": [ 902 + "linux" 903 + ], 904 + "funding": { 905 + "url": "https://opencollective.com/libvips" 906 + } 907 + }, 908 + "node_modules/@img/sharp-libvips-linux-x64": { 909 + "version": "1.2.4", 910 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", 911 + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", 912 + "cpu": [ 913 + "x64" 914 + ], 915 + "dev": true, 916 + "libc": [ 917 + "glibc" 918 + ], 919 + "license": "LGPL-3.0-or-later", 920 + "optional": true, 921 + "os": [ 922 + "linux" 923 + ], 924 + "funding": { 925 + "url": "https://opencollective.com/libvips" 926 + } 927 + }, 928 + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { 929 + "version": "1.2.4", 930 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", 931 + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", 932 + "cpu": [ 933 + "arm64" 934 + ], 935 + "dev": true, 936 + "libc": [ 937 + "musl" 938 + ], 939 + "license": "LGPL-3.0-or-later", 940 + "optional": true, 941 + "os": [ 942 + "linux" 943 + ], 944 + "funding": { 945 + "url": "https://opencollective.com/libvips" 946 + } 947 + }, 948 + "node_modules/@img/sharp-libvips-linuxmusl-x64": { 949 + "version": "1.2.4", 950 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", 951 + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", 952 + "cpu": [ 953 + "x64" 954 + ], 955 + "dev": true, 956 + "libc": [ 957 + "musl" 958 + ], 959 + "license": "LGPL-3.0-or-later", 960 + "optional": true, 961 + "os": [ 962 + "linux" 963 + ], 964 + "funding": { 965 + "url": "https://opencollective.com/libvips" 966 + } 967 + }, 968 + "node_modules/@img/sharp-linux-arm": { 969 + "version": "0.34.5", 970 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", 971 + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", 972 + "cpu": [ 973 + "arm" 974 + ], 975 + "dev": true, 976 + "libc": [ 977 + "glibc" 978 + ], 979 + "license": "Apache-2.0", 980 + "optional": true, 981 + "os": [ 982 + "linux" 983 + ], 984 + "engines": { 985 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 986 + }, 987 + "funding": { 988 + "url": "https://opencollective.com/libvips" 989 + }, 990 + "optionalDependencies": { 991 + "@img/sharp-libvips-linux-arm": "1.2.4" 992 + } 993 + }, 994 + "node_modules/@img/sharp-linux-arm64": { 995 + "version": "0.34.5", 996 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", 997 + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", 998 + "cpu": [ 999 + "arm64" 1000 + ], 1001 + "dev": true, 1002 + "libc": [ 1003 + "glibc" 1004 + ], 1005 + "license": "Apache-2.0", 1006 + "optional": true, 1007 + "os": [ 1008 + "linux" 1009 + ], 1010 + "engines": { 1011 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1012 + }, 1013 + "funding": { 1014 + "url": "https://opencollective.com/libvips" 1015 + }, 1016 + "optionalDependencies": { 1017 + "@img/sharp-libvips-linux-arm64": "1.2.4" 1018 + } 1019 + }, 1020 + "node_modules/@img/sharp-linux-ppc64": { 1021 + "version": "0.34.5", 1022 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", 1023 + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", 1024 + "cpu": [ 1025 + "ppc64" 1026 + ], 1027 + "dev": true, 1028 + "libc": [ 1029 + "glibc" 1030 + ], 1031 + "license": "Apache-2.0", 1032 + "optional": true, 1033 + "os": [ 1034 + "linux" 1035 + ], 1036 + "engines": { 1037 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1038 + }, 1039 + "funding": { 1040 + "url": "https://opencollective.com/libvips" 1041 + }, 1042 + "optionalDependencies": { 1043 + "@img/sharp-libvips-linux-ppc64": "1.2.4" 1044 + } 1045 + }, 1046 + "node_modules/@img/sharp-linux-riscv64": { 1047 + "version": "0.34.5", 1048 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", 1049 + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", 1050 + "cpu": [ 1051 + "riscv64" 1052 + ], 1053 + "dev": true, 1054 + "libc": [ 1055 + "glibc" 1056 + ], 1057 + "license": "Apache-2.0", 1058 + "optional": true, 1059 + "os": [ 1060 + "linux" 1061 + ], 1062 + "engines": { 1063 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1064 + }, 1065 + "funding": { 1066 + "url": "https://opencollective.com/libvips" 1067 + }, 1068 + "optionalDependencies": { 1069 + "@img/sharp-libvips-linux-riscv64": "1.2.4" 1070 + } 1071 + }, 1072 + "node_modules/@img/sharp-linux-s390x": { 1073 + "version": "0.34.5", 1074 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", 1075 + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", 1076 + "cpu": [ 1077 + "s390x" 1078 + ], 1079 + "dev": true, 1080 + "libc": [ 1081 + "glibc" 1082 + ], 1083 + "license": "Apache-2.0", 1084 + "optional": true, 1085 + "os": [ 1086 + "linux" 1087 + ], 1088 + "engines": { 1089 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1090 + }, 1091 + "funding": { 1092 + "url": "https://opencollective.com/libvips" 1093 + }, 1094 + "optionalDependencies": { 1095 + "@img/sharp-libvips-linux-s390x": "1.2.4" 1096 + } 1097 + }, 1098 + "node_modules/@img/sharp-linux-x64": { 1099 + "version": "0.34.5", 1100 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", 1101 + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", 1102 + "cpu": [ 1103 + "x64" 1104 + ], 1105 + "dev": true, 1106 + "libc": [ 1107 + "glibc" 1108 + ], 1109 + "license": "Apache-2.0", 1110 + "optional": true, 1111 + "os": [ 1112 + "linux" 1113 + ], 1114 + "engines": { 1115 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1116 + }, 1117 + "funding": { 1118 + "url": "https://opencollective.com/libvips" 1119 + }, 1120 + "optionalDependencies": { 1121 + "@img/sharp-libvips-linux-x64": "1.2.4" 1122 + } 1123 + }, 1124 + "node_modules/@img/sharp-linuxmusl-arm64": { 1125 + "version": "0.34.5", 1126 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", 1127 + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", 1128 + "cpu": [ 1129 + "arm64" 1130 + ], 1131 + "dev": true, 1132 + "libc": [ 1133 + "musl" 1134 + ], 1135 + "license": "Apache-2.0", 1136 + "optional": true, 1137 + "os": [ 1138 + "linux" 1139 + ], 1140 + "engines": { 1141 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1142 + }, 1143 + "funding": { 1144 + "url": "https://opencollective.com/libvips" 1145 + }, 1146 + "optionalDependencies": { 1147 + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" 1148 + } 1149 + }, 1150 + "node_modules/@img/sharp-linuxmusl-x64": { 1151 + "version": "0.34.5", 1152 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", 1153 + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", 1154 + "cpu": [ 1155 + "x64" 1156 + ], 1157 + "dev": true, 1158 + "libc": [ 1159 + "musl" 1160 + ], 1161 + "license": "Apache-2.0", 1162 + "optional": true, 1163 + "os": [ 1164 + "linux" 1165 + ], 1166 + "engines": { 1167 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1168 + }, 1169 + "funding": { 1170 + "url": "https://opencollective.com/libvips" 1171 + }, 1172 + "optionalDependencies": { 1173 + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" 1174 + } 1175 + }, 1176 + "node_modules/@img/sharp-wasm32": { 1177 + "version": "0.34.5", 1178 + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", 1179 + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", 1180 + "cpu": [ 1181 + "wasm32" 1182 + ], 1183 + "dev": true, 1184 + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", 1185 + "optional": true, 1186 + "dependencies": { 1187 + "@emnapi/runtime": "^1.7.0" 1188 + }, 1189 + "engines": { 1190 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1191 + }, 1192 + "funding": { 1193 + "url": "https://opencollective.com/libvips" 1194 + } 1195 + }, 1196 + "node_modules/@img/sharp-win32-arm64": { 1197 + "version": "0.34.5", 1198 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", 1199 + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", 1200 + "cpu": [ 1201 + "arm64" 1202 + ], 1203 + "dev": true, 1204 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1205 + "optional": true, 1206 + "os": [ 1207 + "win32" 1208 + ], 1209 + "engines": { 1210 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1211 + }, 1212 + "funding": { 1213 + "url": "https://opencollective.com/libvips" 1214 + } 1215 + }, 1216 + "node_modules/@img/sharp-win32-ia32": { 1217 + "version": "0.34.5", 1218 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", 1219 + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", 1220 + "cpu": [ 1221 + "ia32" 1222 + ], 1223 + "dev": true, 1224 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1225 + "optional": true, 1226 + "os": [ 1227 + "win32" 1228 + ], 1229 + "engines": { 1230 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1231 + }, 1232 + "funding": { 1233 + "url": "https://opencollective.com/libvips" 1234 + } 1235 + }, 1236 + "node_modules/@img/sharp-win32-x64": { 1237 + "version": "0.34.5", 1238 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", 1239 + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", 1240 + "cpu": [ 1241 + "x64" 1242 + ], 1243 + "dev": true, 1244 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1245 + "optional": true, 1246 + "os": [ 1247 + "win32" 1248 + ], 1249 + "engines": { 1250 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1251 + }, 1252 + "funding": { 1253 + "url": "https://opencollective.com/libvips" 1254 + } 1255 + }, 1256 + "node_modules/@jridgewell/resolve-uri": { 1257 + "version": "3.1.2", 1258 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 1259 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 1260 + "dev": true, 1261 + "license": "MIT", 1262 + "engines": { 1263 + "node": ">=6.0.0" 1264 + } 1265 + }, 1266 + "node_modules/@jridgewell/sourcemap-codec": { 1267 + "version": "1.5.5", 1268 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", 1269 + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", 1270 + "dev": true, 1271 + "license": "MIT" 1272 + }, 1273 + "node_modules/@jridgewell/trace-mapping": { 1274 + "version": "0.3.9", 1275 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 1276 + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 1277 + "dev": true, 1278 + "license": "MIT", 1279 + "dependencies": { 1280 + "@jridgewell/resolve-uri": "^3.0.3", 1281 + "@jridgewell/sourcemap-codec": "^1.4.10" 1282 + } 1283 + }, 1284 + "node_modules/@napi-rs/wasm-runtime": { 1285 + "version": "1.1.6", 1286 + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", 1287 + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", 1288 + "dev": true, 1289 + "license": "MIT", 1290 + "optional": true, 1291 + "dependencies": { 1292 + "@tybys/wasm-util": "^0.10.3" 1293 + }, 1294 + "funding": { 1295 + "type": "github", 1296 + "url": "https://github.com/sponsors/Brooooooklyn" 1297 + }, 1298 + "peerDependencies": { 1299 + "@emnapi/core": "^1.7.1", 1300 + "@emnapi/runtime": "^1.7.1" 1301 + } 1302 + }, 1303 + "node_modules/@oxc-project/types": { 1304 + "version": "0.137.0", 1305 + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", 1306 + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", 1307 + "dev": true, 1308 + "license": "MIT", 1309 + "funding": { 1310 + "url": "https://github.com/sponsors/Boshen" 1311 + } 1312 + }, 1313 + "node_modules/@poppinss/colors": { 1314 + "version": "4.1.6", 1315 + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", 1316 + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", 1317 + "dev": true, 1318 + "license": "MIT", 1319 + "dependencies": { 1320 + "kleur": "^4.1.5" 1321 + } 1322 + }, 1323 + "node_modules/@poppinss/dumper": { 1324 + "version": "0.6.5", 1325 + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", 1326 + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", 1327 + "dev": true, 1328 + "license": "MIT", 1329 + "dependencies": { 1330 + "@poppinss/colors": "^4.1.5", 1331 + "@sindresorhus/is": "^7.0.2", 1332 + "supports-color": "^10.0.0" 1333 + } 1334 + }, 1335 + "node_modules/@poppinss/exception": { 1336 + "version": "1.2.3", 1337 + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", 1338 + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", 1339 + "dev": true, 1340 + "license": "MIT" 1341 + }, 1342 + "node_modules/@rolldown/binding-android-arm64": { 1343 + "version": "1.1.3", 1344 + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", 1345 + "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", 1346 + "cpu": [ 1347 + "arm64" 1348 + ], 1349 + "dev": true, 1350 + "license": "MIT", 1351 + "optional": true, 1352 + "os": [ 1353 + "android" 1354 + ], 1355 + "engines": { 1356 + "node": "^20.19.0 || >=22.12.0" 1357 + } 1358 + }, 1359 + "node_modules/@rolldown/binding-darwin-arm64": { 1360 + "version": "1.1.3", 1361 + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", 1362 + "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", 1363 + "cpu": [ 1364 + "arm64" 1365 + ], 1366 + "dev": true, 1367 + "license": "MIT", 1368 + "optional": true, 1369 + "os": [ 1370 + "darwin" 1371 + ], 1372 + "engines": { 1373 + "node": "^20.19.0 || >=22.12.0" 1374 + } 1375 + }, 1376 + "node_modules/@rolldown/binding-darwin-x64": { 1377 + "version": "1.1.3", 1378 + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", 1379 + "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", 1380 + "cpu": [ 1381 + "x64" 1382 + ], 1383 + "dev": true, 1384 + "license": "MIT", 1385 + "optional": true, 1386 + "os": [ 1387 + "darwin" 1388 + ], 1389 + "engines": { 1390 + "node": "^20.19.0 || >=22.12.0" 1391 + } 1392 + }, 1393 + "node_modules/@rolldown/binding-freebsd-x64": { 1394 + "version": "1.1.3", 1395 + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", 1396 + "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", 1397 + "cpu": [ 1398 + "x64" 1399 + ], 1400 + "dev": true, 1401 + "license": "MIT", 1402 + "optional": true, 1403 + "os": [ 1404 + "freebsd" 1405 + ], 1406 + "engines": { 1407 + "node": "^20.19.0 || >=22.12.0" 1408 + } 1409 + }, 1410 + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { 1411 + "version": "1.1.3", 1412 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", 1413 + "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", 1414 + "cpu": [ 1415 + "arm" 1416 + ], 1417 + "dev": true, 1418 + "license": "MIT", 1419 + "optional": true, 1420 + "os": [ 1421 + "linux" 1422 + ], 1423 + "engines": { 1424 + "node": "^20.19.0 || >=22.12.0" 1425 + } 1426 + }, 1427 + "node_modules/@rolldown/binding-linux-arm64-gnu": { 1428 + "version": "1.1.3", 1429 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", 1430 + "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", 1431 + "cpu": [ 1432 + "arm64" 1433 + ], 1434 + "dev": true, 1435 + "libc": [ 1436 + "glibc" 1437 + ], 1438 + "license": "MIT", 1439 + "optional": true, 1440 + "os": [ 1441 + "linux" 1442 + ], 1443 + "engines": { 1444 + "node": "^20.19.0 || >=22.12.0" 1445 + } 1446 + }, 1447 + "node_modules/@rolldown/binding-linux-arm64-musl": { 1448 + "version": "1.1.3", 1449 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", 1450 + "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", 1451 + "cpu": [ 1452 + "arm64" 1453 + ], 1454 + "dev": true, 1455 + "libc": [ 1456 + "musl" 1457 + ], 1458 + "license": "MIT", 1459 + "optional": true, 1460 + "os": [ 1461 + "linux" 1462 + ], 1463 + "engines": { 1464 + "node": "^20.19.0 || >=22.12.0" 1465 + } 1466 + }, 1467 + "node_modules/@rolldown/binding-linux-ppc64-gnu": { 1468 + "version": "1.1.3", 1469 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", 1470 + "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", 1471 + "cpu": [ 1472 + "ppc64" 1473 + ], 1474 + "dev": true, 1475 + "libc": [ 1476 + "glibc" 1477 + ], 1478 + "license": "MIT", 1479 + "optional": true, 1480 + "os": [ 1481 + "linux" 1482 + ], 1483 + "engines": { 1484 + "node": "^20.19.0 || >=22.12.0" 1485 + } 1486 + }, 1487 + "node_modules/@rolldown/binding-linux-s390x-gnu": { 1488 + "version": "1.1.3", 1489 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", 1490 + "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", 1491 + "cpu": [ 1492 + "s390x" 1493 + ], 1494 + "dev": true, 1495 + "libc": [ 1496 + "glibc" 1497 + ], 1498 + "license": "MIT", 1499 + "optional": true, 1500 + "os": [ 1501 + "linux" 1502 + ], 1503 + "engines": { 1504 + "node": "^20.19.0 || >=22.12.0" 1505 + } 1506 + }, 1507 + "node_modules/@rolldown/binding-linux-x64-gnu": { 1508 + "version": "1.1.3", 1509 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", 1510 + "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", 1511 + "cpu": [ 1512 + "x64" 1513 + ], 1514 + "dev": true, 1515 + "libc": [ 1516 + "glibc" 1517 + ], 1518 + "license": "MIT", 1519 + "optional": true, 1520 + "os": [ 1521 + "linux" 1522 + ], 1523 + "engines": { 1524 + "node": "^20.19.0 || >=22.12.0" 1525 + } 1526 + }, 1527 + "node_modules/@rolldown/binding-linux-x64-musl": { 1528 + "version": "1.1.3", 1529 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", 1530 + "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", 1531 + "cpu": [ 1532 + "x64" 1533 + ], 1534 + "dev": true, 1535 + "libc": [ 1536 + "musl" 1537 + ], 1538 + "license": "MIT", 1539 + "optional": true, 1540 + "os": [ 1541 + "linux" 1542 + ], 1543 + "engines": { 1544 + "node": "^20.19.0 || >=22.12.0" 1545 + } 1546 + }, 1547 + "node_modules/@rolldown/binding-openharmony-arm64": { 1548 + "version": "1.1.3", 1549 + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", 1550 + "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", 1551 + "cpu": [ 1552 + "arm64" 1553 + ], 1554 + "dev": true, 1555 + "license": "MIT", 1556 + "optional": true, 1557 + "os": [ 1558 + "openharmony" 1559 + ], 1560 + "engines": { 1561 + "node": "^20.19.0 || >=22.12.0" 1562 + } 1563 + }, 1564 + "node_modules/@rolldown/binding-wasm32-wasi": { 1565 + "version": "1.1.3", 1566 + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", 1567 + "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", 1568 + "cpu": [ 1569 + "wasm32" 1570 + ], 1571 + "dev": true, 1572 + "license": "MIT", 1573 + "optional": true, 1574 + "dependencies": { 1575 + "@emnapi/core": "1.11.1", 1576 + "@emnapi/runtime": "1.11.1", 1577 + "@napi-rs/wasm-runtime": "^1.1.6" 1578 + }, 1579 + "engines": { 1580 + "node": "^20.19.0 || >=22.12.0" 1581 + } 1582 + }, 1583 + "node_modules/@rolldown/binding-win32-arm64-msvc": { 1584 + "version": "1.1.3", 1585 + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", 1586 + "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", 1587 + "cpu": [ 1588 + "arm64" 1589 + ], 1590 + "dev": true, 1591 + "license": "MIT", 1592 + "optional": true, 1593 + "os": [ 1594 + "win32" 1595 + ], 1596 + "engines": { 1597 + "node": "^20.19.0 || >=22.12.0" 1598 + } 1599 + }, 1600 + "node_modules/@rolldown/binding-win32-x64-msvc": { 1601 + "version": "1.1.3", 1602 + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", 1603 + "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", 1604 + "cpu": [ 1605 + "x64" 1606 + ], 1607 + "dev": true, 1608 + "license": "MIT", 1609 + "optional": true, 1610 + "os": [ 1611 + "win32" 1612 + ], 1613 + "engines": { 1614 + "node": "^20.19.0 || >=22.12.0" 1615 + } 1616 + }, 1617 + "node_modules/@rolldown/pluginutils": { 1618 + "version": "1.0.1", 1619 + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", 1620 + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", 1621 + "dev": true, 1622 + "license": "MIT" 1623 + }, 1624 + "node_modules/@sindresorhus/is": { 1625 + "version": "7.2.0", 1626 + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", 1627 + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", 1628 + "dev": true, 1629 + "license": "MIT", 1630 + "engines": { 1631 + "node": ">=18" 1632 + }, 1633 + "funding": { 1634 + "url": "https://github.com/sindresorhus/is?sponsor=1" 1635 + } 1636 + }, 1637 + "node_modules/@speed-highlight/core": { 1638 + "version": "1.2.17", 1639 + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.17.tgz", 1640 + "integrity": "sha512-Z92FwKpCtfaW1V0jTU/fh3QzYEZN8wDwrzRIBoADCJfn4mJCNcJN/XegifX7BDrQ8/h9Xh/JnbyMchL0FqXrkg==", 1641 + "dev": true, 1642 + "license": "CC0-1.0" 1643 + }, 1644 + "node_modules/@standard-schema/spec": { 1645 + "version": "1.1.0", 1646 + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", 1647 + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", 1648 + "license": "MIT" 1649 + }, 1650 + "node_modules/@tybys/wasm-util": { 1651 + "version": "0.10.3", 1652 + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", 1653 + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", 1654 + "dev": true, 1655 + "license": "MIT", 1656 + "optional": true, 1657 + "dependencies": { 1658 + "tslib": "^2.4.0" 1659 + } 1660 + }, 1661 + "node_modules/@types/chai": { 1662 + "version": "5.2.3", 1663 + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", 1664 + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", 1665 + "dev": true, 1666 + "license": "MIT", 1667 + "dependencies": { 1668 + "@types/deep-eql": "*", 1669 + "assertion-error": "^2.0.1" 1670 + } 1671 + }, 1672 + "node_modules/@types/deep-eql": { 1673 + "version": "4.0.2", 1674 + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", 1675 + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", 1676 + "dev": true, 1677 + "license": "MIT" 1678 + }, 1679 + "node_modules/@types/estree": { 1680 + "version": "1.0.9", 1681 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", 1682 + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", 1683 + "dev": true, 1684 + "license": "MIT" 1685 + }, 1686 + "node_modules/@vitest/expect": { 1687 + "version": "4.1.9", 1688 + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", 1689 + "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", 1690 + "dev": true, 1691 + "license": "MIT", 1692 + "dependencies": { 1693 + "@standard-schema/spec": "^1.1.0", 1694 + "@types/chai": "^5.2.2", 1695 + "@vitest/spy": "4.1.9", 1696 + "@vitest/utils": "4.1.9", 1697 + "chai": "^6.2.2", 1698 + "tinyrainbow": "^3.1.0" 1699 + }, 1700 + "funding": { 1701 + "url": "https://opencollective.com/vitest" 1702 + } 1703 + }, 1704 + "node_modules/@vitest/mocker": { 1705 + "version": "4.1.9", 1706 + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", 1707 + "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", 1708 + "dev": true, 1709 + "license": "MIT", 1710 + "dependencies": { 1711 + "@vitest/spy": "4.1.9", 1712 + "estree-walker": "^3.0.3", 1713 + "magic-string": "^0.30.21" 1714 + }, 1715 + "funding": { 1716 + "url": "https://opencollective.com/vitest" 1717 + }, 1718 + "peerDependencies": { 1719 + "msw": "^2.4.9", 1720 + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" 1721 + }, 1722 + "peerDependenciesMeta": { 1723 + "msw": { 1724 + "optional": true 1725 + }, 1726 + "vite": { 1727 + "optional": true 1728 + } 1729 + } 1730 + }, 1731 + "node_modules/@vitest/pretty-format": { 1732 + "version": "4.1.9", 1733 + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", 1734 + "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", 1735 + "dev": true, 1736 + "license": "MIT", 1737 + "dependencies": { 1738 + "tinyrainbow": "^3.1.0" 1739 + }, 1740 + "funding": { 1741 + "url": "https://opencollective.com/vitest" 1742 + } 1743 + }, 1744 + "node_modules/@vitest/runner": { 1745 + "version": "4.1.9", 1746 + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", 1747 + "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", 1748 + "dev": true, 1749 + "license": "MIT", 1750 + "dependencies": { 1751 + "@vitest/utils": "4.1.9", 1752 + "pathe": "^2.0.3" 1753 + }, 1754 + "funding": { 1755 + "url": "https://opencollective.com/vitest" 1756 + } 1757 + }, 1758 + "node_modules/@vitest/snapshot": { 1759 + "version": "4.1.9", 1760 + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", 1761 + "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", 1762 + "dev": true, 1763 + "license": "MIT", 1764 + "dependencies": { 1765 + "@vitest/pretty-format": "4.1.9", 1766 + "@vitest/utils": "4.1.9", 1767 + "magic-string": "^0.30.21", 1768 + "pathe": "^2.0.3" 1769 + }, 1770 + "funding": { 1771 + "url": "https://opencollective.com/vitest" 1772 + } 1773 + }, 1774 + "node_modules/@vitest/spy": { 1775 + "version": "4.1.9", 1776 + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", 1777 + "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", 1778 + "dev": true, 1779 + "license": "MIT", 1780 + "funding": { 1781 + "url": "https://opencollective.com/vitest" 1782 + } 1783 + }, 1784 + "node_modules/@vitest/utils": { 1785 + "version": "4.1.9", 1786 + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", 1787 + "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", 1788 + "dev": true, 1789 + "license": "MIT", 1790 + "dependencies": { 1791 + "@vitest/pretty-format": "4.1.9", 1792 + "convert-source-map": "^2.0.0", 1793 + "tinyrainbow": "^3.1.0" 1794 + }, 1795 + "funding": { 1796 + "url": "https://opencollective.com/vitest" 1797 + } 1798 + }, 1799 + "node_modules/assertion-error": { 1800 + "version": "2.0.1", 1801 + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", 1802 + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", 1803 + "dev": true, 1804 + "license": "MIT", 1805 + "engines": { 1806 + "node": ">=12" 1807 + } 1808 + }, 1809 + "node_modules/blake3-wasm": { 1810 + "version": "2.1.5", 1811 + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", 1812 + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", 1813 + "dev": true, 1814 + "license": "MIT" 1815 + }, 1816 + "node_modules/chai": { 1817 + "version": "6.2.2", 1818 + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", 1819 + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", 1820 + "dev": true, 1821 + "license": "MIT", 1822 + "engines": { 1823 + "node": ">=18" 1824 + } 1825 + }, 1826 + "node_modules/convert-source-map": { 1827 + "version": "2.0.0", 1828 + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", 1829 + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", 1830 + "dev": true, 1831 + "license": "MIT" 1832 + }, 1833 + "node_modules/cookie": { 1834 + "version": "1.1.1", 1835 + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", 1836 + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", 1837 + "dev": true, 1838 + "license": "MIT", 1839 + "engines": { 1840 + "node": ">=18" 1841 + }, 1842 + "funding": { 1843 + "type": "opencollective", 1844 + "url": "https://opencollective.com/express" 1845 + } 1846 + }, 1847 + "node_modules/detect-libc": { 1848 + "version": "2.1.2", 1849 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 1850 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 1851 + "dev": true, 1852 + "license": "Apache-2.0", 1853 + "engines": { 1854 + "node": ">=8" 1855 + } 1856 + }, 1857 + "node_modules/error-stack-parser-es": { 1858 + "version": "1.0.5", 1859 + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", 1860 + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", 1861 + "dev": true, 1862 + "license": "MIT", 1863 + "funding": { 1864 + "url": "https://github.com/sponsors/antfu" 1865 + } 1866 + }, 1867 + "node_modules/es-module-lexer": { 1868 + "version": "2.1.0", 1869 + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", 1870 + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", 1871 + "dev": true, 1872 + "license": "MIT" 1873 + }, 1874 + "node_modules/esbuild": { 1875 + "version": "0.28.1", 1876 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", 1877 + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", 1878 + "dev": true, 1879 + "hasInstallScript": true, 1880 + "license": "MIT", 1881 + "bin": { 1882 + "esbuild": "bin/esbuild" 1883 + }, 1884 + "engines": { 1885 + "node": ">=18" 1886 + }, 1887 + "optionalDependencies": { 1888 + "@esbuild/aix-ppc64": "0.28.1", 1889 + "@esbuild/android-arm": "0.28.1", 1890 + "@esbuild/android-arm64": "0.28.1", 1891 + "@esbuild/android-x64": "0.28.1", 1892 + "@esbuild/darwin-arm64": "0.28.1", 1893 + "@esbuild/darwin-x64": "0.28.1", 1894 + "@esbuild/freebsd-arm64": "0.28.1", 1895 + "@esbuild/freebsd-x64": "0.28.1", 1896 + "@esbuild/linux-arm": "0.28.1", 1897 + "@esbuild/linux-arm64": "0.28.1", 1898 + "@esbuild/linux-ia32": "0.28.1", 1899 + "@esbuild/linux-loong64": "0.28.1", 1900 + "@esbuild/linux-mips64el": "0.28.1", 1901 + "@esbuild/linux-ppc64": "0.28.1", 1902 + "@esbuild/linux-riscv64": "0.28.1", 1903 + "@esbuild/linux-s390x": "0.28.1", 1904 + "@esbuild/linux-x64": "0.28.1", 1905 + "@esbuild/netbsd-arm64": "0.28.1", 1906 + "@esbuild/netbsd-x64": "0.28.1", 1907 + "@esbuild/openbsd-arm64": "0.28.1", 1908 + "@esbuild/openbsd-x64": "0.28.1", 1909 + "@esbuild/openharmony-arm64": "0.28.1", 1910 + "@esbuild/sunos-x64": "0.28.1", 1911 + "@esbuild/win32-arm64": "0.28.1", 1912 + "@esbuild/win32-ia32": "0.28.1", 1913 + "@esbuild/win32-x64": "0.28.1" 1914 + } 1915 + }, 1916 + "node_modules/esm-env": { 1917 + "version": "1.2.2", 1918 + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", 1919 + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", 1920 + "license": "MIT" 1921 + }, 1922 + "node_modules/estree-walker": { 1923 + "version": "3.0.3", 1924 + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 1925 + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 1926 + "dev": true, 1927 + "license": "MIT", 1928 + "dependencies": { 1929 + "@types/estree": "^1.0.0" 1930 + } 1931 + }, 1932 + "node_modules/expect-type": { 1933 + "version": "1.3.0", 1934 + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", 1935 + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", 1936 + "dev": true, 1937 + "license": "Apache-2.0", 1938 + "engines": { 1939 + "node": ">=12.0.0" 1940 + } 1941 + }, 1942 + "node_modules/fdir": { 1943 + "version": "6.5.0", 1944 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", 1945 + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", 1946 + "dev": true, 1947 + "license": "MIT", 1948 + "engines": { 1949 + "node": ">=12.0.0" 1950 + }, 1951 + "peerDependencies": { 1952 + "picomatch": "^3 || ^4" 1953 + }, 1954 + "peerDependenciesMeta": { 1955 + "picomatch": { 1956 + "optional": true 1957 + } 1958 + } 1959 + }, 1960 + "node_modules/fsevents": { 1961 + "version": "2.3.3", 1962 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1963 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1964 + "dev": true, 1965 + "hasInstallScript": true, 1966 + "license": "MIT", 1967 + "optional": true, 1968 + "os": [ 1969 + "darwin" 1970 + ], 1971 + "engines": { 1972 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1973 + } 1974 + }, 1975 + "node_modules/kleur": { 1976 + "version": "4.1.5", 1977 + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", 1978 + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", 1979 + "dev": true, 1980 + "license": "MIT", 1981 + "engines": { 1982 + "node": ">=6" 1983 + } 1984 + }, 1985 + "node_modules/lightningcss": { 1986 + "version": "1.32.0", 1987 + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", 1988 + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", 1989 + "dev": true, 1990 + "license": "MPL-2.0", 1991 + "dependencies": { 1992 + "detect-libc": "^2.0.3" 1993 + }, 1994 + "engines": { 1995 + "node": ">= 12.0.0" 1996 + }, 1997 + "funding": { 1998 + "type": "opencollective", 1999 + "url": "https://opencollective.com/parcel" 2000 + }, 2001 + "optionalDependencies": { 2002 + "lightningcss-android-arm64": "1.32.0", 2003 + "lightningcss-darwin-arm64": "1.32.0", 2004 + "lightningcss-darwin-x64": "1.32.0", 2005 + "lightningcss-freebsd-x64": "1.32.0", 2006 + "lightningcss-linux-arm-gnueabihf": "1.32.0", 2007 + "lightningcss-linux-arm64-gnu": "1.32.0", 2008 + "lightningcss-linux-arm64-musl": "1.32.0", 2009 + "lightningcss-linux-x64-gnu": "1.32.0", 2010 + "lightningcss-linux-x64-musl": "1.32.0", 2011 + "lightningcss-win32-arm64-msvc": "1.32.0", 2012 + "lightningcss-win32-x64-msvc": "1.32.0" 2013 + } 2014 + }, 2015 + "node_modules/lightningcss-android-arm64": { 2016 + "version": "1.32.0", 2017 + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", 2018 + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", 2019 + "cpu": [ 2020 + "arm64" 2021 + ], 2022 + "dev": true, 2023 + "license": "MPL-2.0", 2024 + "optional": true, 2025 + "os": [ 2026 + "android" 2027 + ], 2028 + "engines": { 2029 + "node": ">= 12.0.0" 2030 + }, 2031 + "funding": { 2032 + "type": "opencollective", 2033 + "url": "https://opencollective.com/parcel" 2034 + } 2035 + }, 2036 + "node_modules/lightningcss-darwin-arm64": { 2037 + "version": "1.32.0", 2038 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", 2039 + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", 2040 + "cpu": [ 2041 + "arm64" 2042 + ], 2043 + "dev": true, 2044 + "license": "MPL-2.0", 2045 + "optional": true, 2046 + "os": [ 2047 + "darwin" 2048 + ], 2049 + "engines": { 2050 + "node": ">= 12.0.0" 2051 + }, 2052 + "funding": { 2053 + "type": "opencollective", 2054 + "url": "https://opencollective.com/parcel" 2055 + } 2056 + }, 2057 + "node_modules/lightningcss-darwin-x64": { 2058 + "version": "1.32.0", 2059 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", 2060 + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", 2061 + "cpu": [ 2062 + "x64" 2063 + ], 2064 + "dev": true, 2065 + "license": "MPL-2.0", 2066 + "optional": true, 2067 + "os": [ 2068 + "darwin" 2069 + ], 2070 + "engines": { 2071 + "node": ">= 12.0.0" 2072 + }, 2073 + "funding": { 2074 + "type": "opencollective", 2075 + "url": "https://opencollective.com/parcel" 2076 + } 2077 + }, 2078 + "node_modules/lightningcss-freebsd-x64": { 2079 + "version": "1.32.0", 2080 + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", 2081 + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", 2082 + "cpu": [ 2083 + "x64" 2084 + ], 2085 + "dev": true, 2086 + "license": "MPL-2.0", 2087 + "optional": true, 2088 + "os": [ 2089 + "freebsd" 2090 + ], 2091 + "engines": { 2092 + "node": ">= 12.0.0" 2093 + }, 2094 + "funding": { 2095 + "type": "opencollective", 2096 + "url": "https://opencollective.com/parcel" 2097 + } 2098 + }, 2099 + "node_modules/lightningcss-linux-arm-gnueabihf": { 2100 + "version": "1.32.0", 2101 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", 2102 + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", 2103 + "cpu": [ 2104 + "arm" 2105 + ], 2106 + "dev": true, 2107 + "license": "MPL-2.0", 2108 + "optional": true, 2109 + "os": [ 2110 + "linux" 2111 + ], 2112 + "engines": { 2113 + "node": ">= 12.0.0" 2114 + }, 2115 + "funding": { 2116 + "type": "opencollective", 2117 + "url": "https://opencollective.com/parcel" 2118 + } 2119 + }, 2120 + "node_modules/lightningcss-linux-arm64-gnu": { 2121 + "version": "1.32.0", 2122 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", 2123 + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", 2124 + "cpu": [ 2125 + "arm64" 2126 + ], 2127 + "dev": true, 2128 + "libc": [ 2129 + "glibc" 2130 + ], 2131 + "license": "MPL-2.0", 2132 + "optional": true, 2133 + "os": [ 2134 + "linux" 2135 + ], 2136 + "engines": { 2137 + "node": ">= 12.0.0" 2138 + }, 2139 + "funding": { 2140 + "type": "opencollective", 2141 + "url": "https://opencollective.com/parcel" 2142 + } 2143 + }, 2144 + "node_modules/lightningcss-linux-arm64-musl": { 2145 + "version": "1.32.0", 2146 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", 2147 + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", 2148 + "cpu": [ 2149 + "arm64" 2150 + ], 2151 + "dev": true, 2152 + "libc": [ 2153 + "musl" 2154 + ], 2155 + "license": "MPL-2.0", 2156 + "optional": true, 2157 + "os": [ 2158 + "linux" 2159 + ], 2160 + "engines": { 2161 + "node": ">= 12.0.0" 2162 + }, 2163 + "funding": { 2164 + "type": "opencollective", 2165 + "url": "https://opencollective.com/parcel" 2166 + } 2167 + }, 2168 + "node_modules/lightningcss-linux-x64-gnu": { 2169 + "version": "1.32.0", 2170 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", 2171 + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", 2172 + "cpu": [ 2173 + "x64" 2174 + ], 2175 + "dev": true, 2176 + "libc": [ 2177 + "glibc" 2178 + ], 2179 + "license": "MPL-2.0", 2180 + "optional": true, 2181 + "os": [ 2182 + "linux" 2183 + ], 2184 + "engines": { 2185 + "node": ">= 12.0.0" 2186 + }, 2187 + "funding": { 2188 + "type": "opencollective", 2189 + "url": "https://opencollective.com/parcel" 2190 + } 2191 + }, 2192 + "node_modules/lightningcss-linux-x64-musl": { 2193 + "version": "1.32.0", 2194 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", 2195 + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", 2196 + "cpu": [ 2197 + "x64" 2198 + ], 2199 + "dev": true, 2200 + "libc": [ 2201 + "musl" 2202 + ], 2203 + "license": "MPL-2.0", 2204 + "optional": true, 2205 + "os": [ 2206 + "linux" 2207 + ], 2208 + "engines": { 2209 + "node": ">= 12.0.0" 2210 + }, 2211 + "funding": { 2212 + "type": "opencollective", 2213 + "url": "https://opencollective.com/parcel" 2214 + } 2215 + }, 2216 + "node_modules/lightningcss-win32-arm64-msvc": { 2217 + "version": "1.32.0", 2218 + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", 2219 + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", 2220 + "cpu": [ 2221 + "arm64" 2222 + ], 2223 + "dev": true, 2224 + "license": "MPL-2.0", 2225 + "optional": true, 2226 + "os": [ 2227 + "win32" 2228 + ], 2229 + "engines": { 2230 + "node": ">= 12.0.0" 2231 + }, 2232 + "funding": { 2233 + "type": "opencollective", 2234 + "url": "https://opencollective.com/parcel" 2235 + } 2236 + }, 2237 + "node_modules/lightningcss-win32-x64-msvc": { 2238 + "version": "1.32.0", 2239 + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", 2240 + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", 2241 + "cpu": [ 2242 + "x64" 2243 + ], 2244 + "dev": true, 2245 + "license": "MPL-2.0", 2246 + "optional": true, 2247 + "os": [ 2248 + "win32" 2249 + ], 2250 + "engines": { 2251 + "node": ">= 12.0.0" 2252 + }, 2253 + "funding": { 2254 + "type": "opencollective", 2255 + "url": "https://opencollective.com/parcel" 2256 + } 2257 + }, 2258 + "node_modules/magic-string": { 2259 + "version": "0.30.21", 2260 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", 2261 + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", 2262 + "dev": true, 2263 + "license": "MIT", 2264 + "dependencies": { 2265 + "@jridgewell/sourcemap-codec": "^1.5.5" 2266 + } 2267 + }, 2268 + "node_modules/miniflare": { 2269 + "version": "4.20260623.0", 2270 + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260623.0.tgz", 2271 + "integrity": "sha512-p2YTeH01jMiMOjO4v9hb/+GJndja5LCxecGOWCaT9F414PRXgdddLDsK6MnqhGBB5tlU/WoBYIG1XZte5pQzOQ==", 2272 + "dev": true, 2273 + "license": "MIT", 2274 + "dependencies": { 2275 + "@cspotcode/source-map-support": "0.8.1", 2276 + "sharp": "0.34.5", 2277 + "undici": "7.28.0", 2278 + "workerd": "1.20260623.1", 2279 + "ws": "8.21.0", 2280 + "youch": "4.1.0-beta.10" 2281 + }, 2282 + "bin": { 2283 + "miniflare": "bootstrap.js" 2284 + }, 2285 + "engines": { 2286 + "node": ">=22.0.0" 2287 + } 2288 + }, 2289 + "node_modules/nanoid": { 2290 + "version": "3.3.15", 2291 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", 2292 + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", 2293 + "dev": true, 2294 + "funding": [ 2295 + { 2296 + "type": "github", 2297 + "url": "https://github.com/sponsors/ai" 2298 + } 2299 + ], 2300 + "license": "MIT", 2301 + "bin": { 2302 + "nanoid": "bin/nanoid.cjs" 2303 + }, 2304 + "engines": { 2305 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 2306 + } 2307 + }, 2308 + "node_modules/obug": { 2309 + "version": "2.1.3", 2310 + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", 2311 + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", 2312 + "dev": true, 2313 + "funding": [ 2314 + "https://github.com/sponsors/sxzz", 2315 + "https://opencollective.com/debug" 2316 + ], 2317 + "license": "MIT", 2318 + "engines": { 2319 + "node": ">=12.20.0" 2320 + } 2321 + }, 2322 + "node_modules/path-to-regexp": { 2323 + "version": "6.3.0", 2324 + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", 2325 + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", 2326 + "dev": true, 2327 + "license": "MIT" 2328 + }, 2329 + "node_modules/pathe": { 2330 + "version": "2.0.3", 2331 + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", 2332 + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", 2333 + "dev": true, 2334 + "license": "MIT" 2335 + }, 2336 + "node_modules/picocolors": { 2337 + "version": "1.1.1", 2338 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 2339 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 2340 + "dev": true, 2341 + "license": "ISC" 2342 + }, 2343 + "node_modules/picomatch": { 2344 + "version": "4.0.4", 2345 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", 2346 + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", 2347 + "dev": true, 2348 + "license": "MIT", 2349 + "engines": { 2350 + "node": ">=12" 2351 + }, 2352 + "funding": { 2353 + "url": "https://github.com/sponsors/jonschlinkert" 2354 + } 2355 + }, 2356 + "node_modules/postcss": { 2357 + "version": "8.5.15", 2358 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", 2359 + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", 2360 + "dev": true, 2361 + "funding": [ 2362 + { 2363 + "type": "opencollective", 2364 + "url": "https://opencollective.com/postcss/" 2365 + }, 2366 + { 2367 + "type": "tidelift", 2368 + "url": "https://tidelift.com/funding/github/npm/postcss" 2369 + }, 2370 + { 2371 + "type": "github", 2372 + "url": "https://github.com/sponsors/ai" 2373 + } 2374 + ], 2375 + "license": "MIT", 2376 + "dependencies": { 2377 + "nanoid": "^3.3.12", 2378 + "picocolors": "^1.1.1", 2379 + "source-map-js": "^1.2.1" 2380 + }, 2381 + "engines": { 2382 + "node": "^10 || ^12 || >=14" 2383 + } 2384 + }, 2385 + "node_modules/rolldown": { 2386 + "version": "1.1.3", 2387 + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", 2388 + "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", 2389 + "dev": true, 2390 + "license": "MIT", 2391 + "dependencies": { 2392 + "@oxc-project/types": "=0.137.0", 2393 + "@rolldown/pluginutils": "^1.0.0" 2394 + }, 2395 + "bin": { 2396 + "rolldown": "bin/cli.mjs" 2397 + }, 2398 + "engines": { 2399 + "node": "^20.19.0 || >=22.12.0" 2400 + }, 2401 + "optionalDependencies": { 2402 + "@rolldown/binding-android-arm64": "1.1.3", 2403 + "@rolldown/binding-darwin-arm64": "1.1.3", 2404 + "@rolldown/binding-darwin-x64": "1.1.3", 2405 + "@rolldown/binding-freebsd-x64": "1.1.3", 2406 + "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", 2407 + "@rolldown/binding-linux-arm64-gnu": "1.1.3", 2408 + "@rolldown/binding-linux-arm64-musl": "1.1.3", 2409 + "@rolldown/binding-linux-ppc64-gnu": "1.1.3", 2410 + "@rolldown/binding-linux-s390x-gnu": "1.1.3", 2411 + "@rolldown/binding-linux-x64-gnu": "1.1.3", 2412 + "@rolldown/binding-linux-x64-musl": "1.1.3", 2413 + "@rolldown/binding-openharmony-arm64": "1.1.3", 2414 + "@rolldown/binding-wasm32-wasi": "1.1.3", 2415 + "@rolldown/binding-win32-arm64-msvc": "1.1.3", 2416 + "@rolldown/binding-win32-x64-msvc": "1.1.3" 2417 + } 2418 + }, 2419 + "node_modules/semver": { 2420 + "version": "7.8.5", 2421 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", 2422 + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", 2423 + "dev": true, 2424 + "license": "ISC", 2425 + "bin": { 2426 + "semver": "bin/semver.js" 2427 + }, 2428 + "engines": { 2429 + "node": ">=10" 2430 + } 2431 + }, 2432 + "node_modules/sharp": { 2433 + "version": "0.34.5", 2434 + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", 2435 + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", 2436 + "dev": true, 2437 + "hasInstallScript": true, 2438 + "license": "Apache-2.0", 2439 + "dependencies": { 2440 + "@img/colour": "^1.0.0", 2441 + "detect-libc": "^2.1.2", 2442 + "semver": "^7.7.3" 2443 + }, 2444 + "engines": { 2445 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 2446 + }, 2447 + "funding": { 2448 + "url": "https://opencollective.com/libvips" 2449 + }, 2450 + "optionalDependencies": { 2451 + "@img/sharp-darwin-arm64": "0.34.5", 2452 + "@img/sharp-darwin-x64": "0.34.5", 2453 + "@img/sharp-libvips-darwin-arm64": "1.2.4", 2454 + "@img/sharp-libvips-darwin-x64": "1.2.4", 2455 + "@img/sharp-libvips-linux-arm": "1.2.4", 2456 + "@img/sharp-libvips-linux-arm64": "1.2.4", 2457 + "@img/sharp-libvips-linux-ppc64": "1.2.4", 2458 + "@img/sharp-libvips-linux-riscv64": "1.2.4", 2459 + "@img/sharp-libvips-linux-s390x": "1.2.4", 2460 + "@img/sharp-libvips-linux-x64": "1.2.4", 2461 + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", 2462 + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", 2463 + "@img/sharp-linux-arm": "0.34.5", 2464 + "@img/sharp-linux-arm64": "0.34.5", 2465 + "@img/sharp-linux-ppc64": "0.34.5", 2466 + "@img/sharp-linux-riscv64": "0.34.5", 2467 + "@img/sharp-linux-s390x": "0.34.5", 2468 + "@img/sharp-linux-x64": "0.34.5", 2469 + "@img/sharp-linuxmusl-arm64": "0.34.5", 2470 + "@img/sharp-linuxmusl-x64": "0.34.5", 2471 + "@img/sharp-wasm32": "0.34.5", 2472 + "@img/sharp-win32-arm64": "0.34.5", 2473 + "@img/sharp-win32-ia32": "0.34.5", 2474 + "@img/sharp-win32-x64": "0.34.5" 2475 + } 2476 + }, 2477 + "node_modules/siginfo": { 2478 + "version": "2.0.0", 2479 + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", 2480 + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", 2481 + "dev": true, 2482 + "license": "ISC" 2483 + }, 2484 + "node_modules/source-map-js": { 2485 + "version": "1.2.1", 2486 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 2487 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 2488 + "dev": true, 2489 + "license": "BSD-3-Clause", 2490 + "engines": { 2491 + "node": ">=0.10.0" 2492 + } 2493 + }, 2494 + "node_modules/stackback": { 2495 + "version": "0.0.2", 2496 + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", 2497 + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", 2498 + "dev": true, 2499 + "license": "MIT" 2500 + }, 2501 + "node_modules/std-env": { 2502 + "version": "4.1.0", 2503 + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", 2504 + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", 2505 + "dev": true, 2506 + "license": "MIT" 2507 + }, 2508 + "node_modules/supports-color": { 2509 + "version": "10.2.2", 2510 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", 2511 + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", 2512 + "dev": true, 2513 + "license": "MIT", 2514 + "engines": { 2515 + "node": ">=18" 2516 + }, 2517 + "funding": { 2518 + "url": "https://github.com/chalk/supports-color?sponsor=1" 2519 + } 2520 + }, 2521 + "node_modules/tinybench": { 2522 + "version": "2.9.0", 2523 + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", 2524 + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", 2525 + "dev": true, 2526 + "license": "MIT" 2527 + }, 2528 + "node_modules/tinyexec": { 2529 + "version": "1.2.4", 2530 + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", 2531 + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", 2532 + "dev": true, 2533 + "license": "MIT", 2534 + "engines": { 2535 + "node": ">=18" 2536 + } 2537 + }, 2538 + "node_modules/tinyglobby": { 2539 + "version": "0.2.17", 2540 + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", 2541 + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", 2542 + "dev": true, 2543 + "license": "MIT", 2544 + "dependencies": { 2545 + "fdir": "^6.5.0", 2546 + "picomatch": "^4.0.4" 2547 + }, 2548 + "engines": { 2549 + "node": ">=12.0.0" 2550 + }, 2551 + "funding": { 2552 + "url": "https://github.com/sponsors/SuperchupuDev" 2553 + } 2554 + }, 2555 + "node_modules/tinyrainbow": { 2556 + "version": "3.1.0", 2557 + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", 2558 + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", 2559 + "dev": true, 2560 + "license": "MIT", 2561 + "engines": { 2562 + "node": ">=14.0.0" 2563 + } 2564 + }, 2565 + "node_modules/tslib": { 2566 + "version": "2.8.1", 2567 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 2568 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 2569 + "dev": true, 2570 + "license": "0BSD", 2571 + "optional": true 2572 + }, 2573 + "node_modules/typescript": { 2574 + "version": "6.0.3", 2575 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", 2576 + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", 2577 + "devOptional": true, 2578 + "license": "Apache-2.0", 2579 + "bin": { 2580 + "tsc": "bin/tsc", 2581 + "tsserver": "bin/tsserver" 2582 + }, 2583 + "engines": { 2584 + "node": ">=14.17" 2585 + } 2586 + }, 2587 + "node_modules/undici": { 2588 + "version": "7.28.0", 2589 + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", 2590 + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", 2591 + "dev": true, 2592 + "license": "MIT", 2593 + "engines": { 2594 + "node": ">=20.18.1" 2595 + } 2596 + }, 2597 + "node_modules/unenv": { 2598 + "version": "2.0.0-rc.24", 2599 + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", 2600 + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", 2601 + "dev": true, 2602 + "license": "MIT", 2603 + "dependencies": { 2604 + "pathe": "^2.0.3" 2605 + } 2606 + }, 2607 + "node_modules/unicode-segmenter": { 2608 + "version": "0.14.5", 2609 + "resolved": "https://registry.npmjs.org/unicode-segmenter/-/unicode-segmenter-0.14.5.tgz", 2610 + "integrity": "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==", 2611 + "license": "MIT" 2612 + }, 2613 + "node_modules/valibot": { 2614 + "version": "1.4.1", 2615 + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.1.tgz", 2616 + "integrity": "sha512-klCmFTz2jeDluy9RwX+F884TCiogtdBJ/YaxSx1EOBYXa3NXNWj8kR1jjN8rzluwojJVWWaHJ4r1U5LfICnM3g==", 2617 + "license": "MIT", 2618 + "peerDependencies": { 2619 + "typescript": ">=5" 2620 + }, 2621 + "peerDependenciesMeta": { 2622 + "typescript": { 2623 + "optional": true 2624 + } 2625 + } 2626 + }, 2627 + "node_modules/vite": { 2628 + "version": "8.1.0", 2629 + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", 2630 + "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", 2631 + "dev": true, 2632 + "license": "MIT", 2633 + "dependencies": { 2634 + "lightningcss": "^1.32.0", 2635 + "picomatch": "^4.0.4", 2636 + "postcss": "^8.5.15", 2637 + "rolldown": "~1.1.2", 2638 + "tinyglobby": "^0.2.17" 2639 + }, 2640 + "bin": { 2641 + "vite": "bin/vite.js" 2642 + }, 2643 + "engines": { 2644 + "node": "^20.19.0 || >=22.12.0" 2645 + }, 2646 + "funding": { 2647 + "url": "https://github.com/vitejs/vite?sponsor=1" 2648 + }, 2649 + "optionalDependencies": { 2650 + "fsevents": "~2.3.3" 2651 + }, 2652 + "peerDependencies": { 2653 + "@types/node": "^20.19.0 || >=22.12.0", 2654 + "@vitejs/devtools": "^0.3.0", 2655 + "esbuild": "^0.27.0 || ^0.28.0", 2656 + "jiti": ">=1.21.0", 2657 + "less": "^4.0.0", 2658 + "sass": "^1.70.0", 2659 + "sass-embedded": "^1.70.0", 2660 + "stylus": ">=0.54.8", 2661 + "sugarss": "^5.0.0", 2662 + "terser": "^5.16.0", 2663 + "tsx": "^4.8.1", 2664 + "yaml": "^2.4.2" 2665 + }, 2666 + "peerDependenciesMeta": { 2667 + "@types/node": { 2668 + "optional": true 2669 + }, 2670 + "@vitejs/devtools": { 2671 + "optional": true 2672 + }, 2673 + "esbuild": { 2674 + "optional": true 2675 + }, 2676 + "jiti": { 2677 + "optional": true 2678 + }, 2679 + "less": { 2680 + "optional": true 2681 + }, 2682 + "sass": { 2683 + "optional": true 2684 + }, 2685 + "sass-embedded": { 2686 + "optional": true 2687 + }, 2688 + "stylus": { 2689 + "optional": true 2690 + }, 2691 + "sugarss": { 2692 + "optional": true 2693 + }, 2694 + "terser": { 2695 + "optional": true 2696 + }, 2697 + "tsx": { 2698 + "optional": true 2699 + }, 2700 + "yaml": { 2701 + "optional": true 2702 + } 2703 + } 2704 + }, 2705 + "node_modules/vitest": { 2706 + "version": "4.1.9", 2707 + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", 2708 + "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", 2709 + "dev": true, 2710 + "license": "MIT", 2711 + "dependencies": { 2712 + "@vitest/expect": "4.1.9", 2713 + "@vitest/mocker": "4.1.9", 2714 + "@vitest/pretty-format": "4.1.9", 2715 + "@vitest/runner": "4.1.9", 2716 + "@vitest/snapshot": "4.1.9", 2717 + "@vitest/spy": "4.1.9", 2718 + "@vitest/utils": "4.1.9", 2719 + "es-module-lexer": "^2.0.0", 2720 + "expect-type": "^1.3.0", 2721 + "magic-string": "^0.30.21", 2722 + "obug": "^2.1.1", 2723 + "pathe": "^2.0.3", 2724 + "picomatch": "^4.0.3", 2725 + "std-env": "^4.0.0-rc.1", 2726 + "tinybench": "^2.9.0", 2727 + "tinyexec": "^1.0.2", 2728 + "tinyglobby": "^0.2.15", 2729 + "tinyrainbow": "^3.1.0", 2730 + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", 2731 + "why-is-node-running": "^2.3.0" 2732 + }, 2733 + "bin": { 2734 + "vitest": "vitest.mjs" 2735 + }, 2736 + "engines": { 2737 + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" 2738 + }, 2739 + "funding": { 2740 + "url": "https://opencollective.com/vitest" 2741 + }, 2742 + "peerDependencies": { 2743 + "@edge-runtime/vm": "*", 2744 + "@opentelemetry/api": "^1.9.0", 2745 + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", 2746 + "@vitest/browser-playwright": "4.1.9", 2747 + "@vitest/browser-preview": "4.1.9", 2748 + "@vitest/browser-webdriverio": "4.1.9", 2749 + "@vitest/coverage-istanbul": "4.1.9", 2750 + "@vitest/coverage-v8": "4.1.9", 2751 + "@vitest/ui": "4.1.9", 2752 + "happy-dom": "*", 2753 + "jsdom": "*", 2754 + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" 2755 + }, 2756 + "peerDependenciesMeta": { 2757 + "@edge-runtime/vm": { 2758 + "optional": true 2759 + }, 2760 + "@opentelemetry/api": { 2761 + "optional": true 2762 + }, 2763 + "@types/node": { 2764 + "optional": true 2765 + }, 2766 + "@vitest/browser-playwright": { 2767 + "optional": true 2768 + }, 2769 + "@vitest/browser-preview": { 2770 + "optional": true 2771 + }, 2772 + "@vitest/browser-webdriverio": { 2773 + "optional": true 2774 + }, 2775 + "@vitest/coverage-istanbul": { 2776 + "optional": true 2777 + }, 2778 + "@vitest/coverage-v8": { 2779 + "optional": true 2780 + }, 2781 + "@vitest/ui": { 2782 + "optional": true 2783 + }, 2784 + "happy-dom": { 2785 + "optional": true 2786 + }, 2787 + "jsdom": { 2788 + "optional": true 2789 + }, 2790 + "vite": { 2791 + "optional": false 2792 + } 2793 + } 2794 + }, 2795 + "node_modules/why-is-node-running": { 2796 + "version": "2.3.0", 2797 + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", 2798 + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", 2799 + "dev": true, 2800 + "license": "MIT", 2801 + "dependencies": { 2802 + "siginfo": "^2.0.0", 2803 + "stackback": "0.0.2" 2804 + }, 2805 + "bin": { 2806 + "why-is-node-running": "cli.js" 2807 + }, 2808 + "engines": { 2809 + "node": ">=8" 2810 + } 2811 + }, 2812 + "node_modules/workerd": { 2813 + "version": "1.20260623.1", 2814 + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260623.1.tgz", 2815 + "integrity": "sha512-9SJsdTSsehhqc26TUJIzyi1XgyYeqFym4hinZnWoAP1BkhEoMQ5Ygz7Xw9T+2ecU+y409JBEScBgWTdZ06mBrg==", 2816 + "dev": true, 2817 + "hasInstallScript": true, 2818 + "license": "Apache-2.0", 2819 + "bin": { 2820 + "workerd": "bin/workerd" 2821 + }, 2822 + "engines": { 2823 + "node": ">=16" 2824 + }, 2825 + "optionalDependencies": { 2826 + "@cloudflare/workerd-darwin-64": "1.20260623.1", 2827 + "@cloudflare/workerd-darwin-arm64": "1.20260623.1", 2828 + "@cloudflare/workerd-linux-64": "1.20260623.1", 2829 + "@cloudflare/workerd-linux-arm64": "1.20260623.1", 2830 + "@cloudflare/workerd-windows-64": "1.20260623.1" 2831 + } 2832 + }, 2833 + "node_modules/wrangler": { 2834 + "version": "4.104.0", 2835 + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.104.0.tgz", 2836 + "integrity": "sha512-xCfbg2Oj93Bc7EMryFaSeRGDgV96dzrWoaK5q2q5XLEvumO4mysNP/1MDue0GUozEJAI6Z6vrGyYPLmfET/0sg==", 2837 + "dev": true, 2838 + "license": "MIT OR Apache-2.0", 2839 + "dependencies": { 2840 + "@cloudflare/kv-asset-handler": "0.5.0", 2841 + "@cloudflare/unenv-preset": "2.16.1", 2842 + "blake3-wasm": "2.1.5", 2843 + "esbuild": "0.28.1", 2844 + "miniflare": "4.20260623.0", 2845 + "path-to-regexp": "6.3.0", 2846 + "unenv": "2.0.0-rc.24", 2847 + "workerd": "1.20260623.1" 2848 + }, 2849 + "bin": { 2850 + "cf-wrangler": "bin/cf-wrangler.js", 2851 + "wrangler": "bin/wrangler.js", 2852 + "wrangler2": "bin/wrangler.js" 2853 + }, 2854 + "engines": { 2855 + "node": ">=22.0.0" 2856 + }, 2857 + "optionalDependencies": { 2858 + "fsevents": "2.3.3" 2859 + }, 2860 + "peerDependencies": { 2861 + "@cloudflare/workers-types": "^4.20260623.1" 2862 + }, 2863 + "peerDependenciesMeta": { 2864 + "@cloudflare/workers-types": { 2865 + "optional": true 2866 + } 2867 + } 2868 + }, 2869 + "node_modules/ws": { 2870 + "version": "8.21.0", 2871 + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", 2872 + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", 2873 + "dev": true, 2874 + "license": "MIT", 2875 + "engines": { 2876 + "node": ">=10.0.0" 2877 + }, 2878 + "peerDependencies": { 2879 + "bufferutil": "^4.0.1", 2880 + "utf-8-validate": ">=5.0.2" 2881 + }, 2882 + "peerDependenciesMeta": { 2883 + "bufferutil": { 2884 + "optional": true 2885 + }, 2886 + "utf-8-validate": { 2887 + "optional": true 2888 + } 2889 + } 2890 + }, 2891 + "node_modules/youch": { 2892 + "version": "4.1.0-beta.10", 2893 + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", 2894 + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", 2895 + "dev": true, 2896 + "license": "MIT", 2897 + "dependencies": { 2898 + "@poppinss/colors": "^4.1.5", 2899 + "@poppinss/dumper": "^0.6.4", 2900 + "@speed-highlight/core": "^1.2.7", 2901 + "cookie": "^1.0.2", 2902 + "youch-core": "^0.3.3" 2903 + } 2904 + }, 2905 + "node_modules/youch-core": { 2906 + "version": "0.3.3", 2907 + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", 2908 + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", 2909 + "dev": true, 2910 + "license": "MIT", 2911 + "dependencies": { 2912 + "@poppinss/exception": "^1.2.2", 2913 + "error-stack-parser-es": "^1.0.5" 2914 + } 2915 + } 2916 + } 2917 + }
+24
package.json
··· 1 + { 2 + "name": "atmot-bot", 3 + "version": "1.0.0", 4 + "description": "Daily Bluesky bot for AT Mot — invites players and congratulates yesterday's solvers.", 5 + "private": true, 6 + "type": "module", 7 + "scripts": { 8 + "test": "vitest run", 9 + "typecheck": "tsc --noEmit", 10 + "dev": "wrangler dev --test-scheduled", 11 + "deploy": "wrangler deploy" 12 + }, 13 + "dependencies": { 14 + "@atcute/atproto": "^4.0.2", 15 + "@atcute/client": "^5.1.0", 16 + "@atcute/identity-resolver": "^2.0.0" 17 + }, 18 + "devDependencies": { 19 + "@cloudflare/workers-types": "^4.20240000.0", 20 + "typescript": "^6.0.3", 21 + "vitest": "^4.1.9", 22 + "wrangler": "^4.103.0" 23 + } 24 + }
+76
src/compose.ts
··· 1 + import { ORIGIN, type Lang, type YesterdayCounts } from './config.js'; 2 + import { linkFacet, type FacetLink } from './facets.js'; 3 + 4 + export interface ComposeInput { 5 + lang: Lang; 6 + todayN: number; 7 + /** Yesterday's counts, or null on day 1 when there is no previous puzzle. */ 8 + yesterday: YesterdayCounts | null; 9 + } 10 + 11 + export interface ComposedPost { 12 + text: string; 13 + facets: FacetLink[]; 14 + langs: [Lang]; 15 + } 16 + 17 + /** Verbatim in the post text; also the idempotency marker. */ 18 + export function postMarker(lang: Lang, todayN: number): string { 19 + return lang === 'fr' ? `AT Mot n°${todayN}` : `AT Mot #${todayN}`; 20 + } 21 + 22 + function inviteLine(lang: Lang, todayN: number): string { 23 + const marker = postMarker(lang, todayN); 24 + return lang === 'fr' 25 + ? `🟩 ${marker} est en ligne ! Six essais pour deviner le mot de cinq lettres du jour.` 26 + : `🟩 ${marker} is live! Six tries to guess today's five-letter word.`; 27 + } 28 + 29 + /** The congrats sentence, or null when there's nothing meaningful to say. */ 30 + function congratsLine(lang: Lang, c: YesterdayCounts): string | null { 31 + const { players, solvers, sampled } = c; 32 + if (players == null || players === 0) return null; 33 + 34 + if (solvers === 0) { 35 + return lang === 'fr' 36 + ? `Personne n'a trouvé le mot d'hier. Nouveau départ aujourd'hui !` 37 + : `Nobody cracked yesterday's word. Fresh start today!`; 38 + } 39 + 40 + if (sampled) { 41 + return lang === 'fr' 42 + ? `Bravo aux ${solvers}+ qui ont trouvé le mot d'hier. Saurez-vous les rejoindre aujourd'hui ?` 43 + : `Congrats to the ${solvers}+ of you who cracked yesterday's word. Can you join them today?`; 44 + } 45 + 46 + const nonSolvers = Math.max(0, players - solvers); 47 + 48 + if (lang === 'fr') { 49 + const who = solvers === 1 ? 'au seul joueur' : `aux ${solvers}`; 50 + const verb = solvers === 1 ? 'a' : 'ont'; 51 + let line = `Bravo ${who} qui ${verb} trouvé le mot d'hier.`; 52 + if (nonSolvers > 0) { 53 + const ns = nonSolvers === 1 ? 'Et au seul qui a séché' : `Et aux ${nonSolvers} qui ont séché`; 54 + line += ` ${ns}, meilleure chance aujourd'hui !`; 55 + } 56 + return line; 57 + } 58 + 59 + const who = solvers === 1 ? 'the one player' : `the ${solvers} of you`; 60 + let line = `Congrats to ${who} who cracked yesterday's word.`; 61 + if (nonSolvers > 0) { 62 + const ns = nonSolvers === 1 ? "And to the one who didn't" : `And to the ${nonSolvers} who didn't`; 63 + line += ` ${ns}, better luck today!`; 64 + } 65 + return line; 66 + } 67 + 68 + export function composePost(input: ComposeInput): ComposedPost { 69 + const { lang, todayN, yesterday } = input; 70 + const congrats = yesterday ? congratsLine(lang, yesterday) : null; 71 + const playLine = lang === 'fr' ? `Jouez : ${ORIGIN}` : `Play: ${ORIGIN}`; 72 + const tags = lang === 'fr' ? '#JeuDeMots #atproto' : '#WordGame #atproto'; 73 + 74 + const text = [inviteLine(lang, todayN), congrats, playLine, tags].filter(Boolean).join('\n'); 75 + return { text, facets: linkFacet(text, ORIGIN), langs: [lang] }; 76 + }
+63
src/config.ts
··· 1 + /** 2 + * Frozen constants and puzzle math, duplicated from the AT Mot app. 3 + * Every value here is immutable post-launch (it indexes historical data), 4 + * so duplicating it into the bot carries no divergence risk. 5 + */ 6 + 7 + export const LANGS = ['en', 'fr'] as const; 8 + export type Lang = (typeof LANGS)[number]; 9 + 10 + export const DOMAIN = 'atmot.herve.bzh'; 11 + export const ORIGIN = `https://${DOMAIN}`; 12 + 13 + export const NSID_AUTHORITY = 'bzh.herve.atmot'; 14 + export const COLLECTION = { result: `${NSID_AUTHORITY}.result` } as const; 15 + 16 + /** Epoch: 2026-06-23 (UTC) midnight. Launch day = puzzle #1. (Month is 0-indexed: 5 = June.) */ 17 + export const EPOCH_UTC_MS = Date.UTC(2026, 5, 23); 18 + 19 + const MS_PER_DAY = 86_400_000; 20 + 21 + /** Whole UTC days from the epoch to `at` (epoch day = 0). */ 22 + export function daysSinceEpoch(at: number = Date.now()): number { 23 + const utcMidnight = Math.floor(at / MS_PER_DAY) * MS_PER_DAY; 24 + return Math.floor((utcMidnight - EPOCH_UTC_MS) / MS_PER_DAY); 25 + } 26 + 27 + /** Puzzle number for a UTC day. Epoch = 1; days before the epoch are < 1. */ 28 + export function puzzleNumberFor(at: number = Date.now()): number { 29 + return daysSinceEpoch(at) + 1; 30 + } 31 + 32 + /** Canonical, frozen leaderboard target. Constellation compares this literally. */ 33 + export function puzzleTarget(lang: Lang, puzzleNumber: number): string { 34 + return `${ORIGIN}/p/${lang}/${puzzleNumber}`; 35 + } 36 + 37 + export const CONSTELLATION_BASE = 'https://constellation.microcosm.blue'; 38 + export const APPVIEW_URL = 'https://public.api.bsky.app'; 39 + export const USER_AGENT = `atmot-bot/1.0 (+${ORIGIN}; daily word game bot)`; 40 + 41 + /** Free Workers plan: keep ~2 subrequests/record under the 50/invocation cap. */ 42 + export const SOLVER_SAMPLE_CAP = 20; 43 + 44 + /** The result record shape we read from each player's PDS (colours only; subset we use). */ 45 + export interface ResultRecord { 46 + $type: 'bzh.herve.atmot.result'; 47 + lang: string; 48 + puzzleNumber: number; 49 + solved: boolean; 50 + guessCount?: number; 51 + puzzleTarget: string; 52 + createdAt: string; 53 + } 54 + 55 + /** Yesterday's aggregate, produced by counts.ts and consumed by compose.ts. */ 56 + export interface YesterdayCounts { 57 + /** Distinct players who recorded a result (wins + losses); null if Constellation was unreachable. */ 58 + players: number | null; 59 + /** Players who solved, counted from the sampled records (≤ SOLVER_SAMPLE_CAP). */ 60 + solvers: number; 61 + /** True if the sample hit the cap (more results may exist than were counted). */ 62 + sampled: boolean; 63 + }
+80
src/constellation.ts
··· 1 + /** 2 + * Constellation (microcosm) backlink-index client. Returns null/[] on any 3 + * failure so the bot degrades to no-congrats copy rather than crashing. 4 + */ 5 + import { CONSTELLATION_BASE, USER_AGENT, COLLECTION, puzzleTarget, type Lang } from './config.js'; 6 + 7 + export interface BacklinkRecord { 8 + did: string; 9 + collection: string; 10 + rkey: string; 11 + } 12 + 13 + /** Source path Constellation indexes for the result record's leaderboard link. */ 14 + const RESULT_TARGET_PATH = 'puzzleTarget'; 15 + 16 + async function get(path: string, params: Record<string, string>): Promise<any | null> { 17 + const url = new URL(path, CONSTELLATION_BASE); 18 + for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v); 19 + try { 20 + const res = await fetch(url, { headers: { accept: 'application/json', 'user-agent': USER_AGENT } }); 21 + if (!res.ok) return null; 22 + return await res.json(); 23 + } catch { 24 + return null; 25 + } 26 + } 27 + 28 + /** Distinct DIDs linking to `target`. Null on failure. */ 29 + async function countDistinctDids(target: string, collection: string, dottedPath: string): Promise<number | null> { 30 + const data = await get('/links/count/distinct-dids', { 31 + target, 32 + collection, 33 + path: dottedPath.startsWith('.') ? dottedPath : `.${dottedPath}`, 34 + }); 35 + return typeof data?.total === 'number' ? data.total : null; 36 + } 37 + 38 + async function getBacklinksPage( 39 + target: string, 40 + collection: string, 41 + path: string, 42 + opts: { limit: number; cursor?: string }, 43 + ): Promise<{ records: BacklinkRecord[]; cursor: string | null } | null> { 44 + const params: Record<string, string> = { 45 + subject: target, 46 + source: `${collection}:${path}`, 47 + limit: String(opts.limit), 48 + }; 49 + if (opts.cursor) params.cursor = opts.cursor; 50 + const data = await get('/xrpc/blue.microcosm.links.getBacklinks', params); 51 + if (!data || !Array.isArray(data.records)) return null; 52 + return { records: data.records as BacklinkRecord[], cursor: data.cursor ?? null }; 53 + } 54 + 55 + async function collectBacklinks(target: string, collection: string, path: string, cap: number): Promise<BacklinkRecord[]> { 56 + const out: BacklinkRecord[] = []; 57 + let cursor: string | undefined; 58 + for (let i = 0; i < 10 && out.length < cap; i++) { 59 + const page = await getBacklinksPage(target, collection, path, { limit: Math.min(100, cap), cursor }); 60 + if (!page) break; 61 + out.push(...page.records); 62 + if (!page.cursor) break; 63 + cursor = page.cursor; 64 + } 65 + return out.slice(0, cap); 66 + } 67 + 68 + /** Distinct players who recorded a result for this puzzle (wins + losses). Null on failure. */ 69 + export function dailyPlayerCount(lang: Lang, puzzleNumber: number): Promise<number | null> { 70 + return countDistinctDids(puzzleTarget(lang, puzzleNumber), COLLECTION.result, RESULT_TARGET_PATH); 71 + } 72 + 73 + /** Up to `cap` result-record backlinks for this puzzle. */ 74 + export function dailyResultBacklinks(lang: Lang, puzzleNumber: number, cap: number): Promise<BacklinkRecord[]> { 75 + return collectBacklinks(puzzleTarget(lang, puzzleNumber), COLLECTION.result, RESULT_TARGET_PATH, cap); 76 + } 77 + 78 + export function backlinkUri(r: BacklinkRecord): string { 79 + return `at://${r.did}/${r.collection}/${r.rkey}`; 80 + }
+25
src/counts.ts
··· 1 + import { SOLVER_SAMPLE_CAP, type Lang, type ResultRecord, type YesterdayCounts } from './config.js'; 2 + import { dailyPlayerCount, dailyResultBacklinks, backlinkUri } from './constellation.js'; 3 + import { getRecordByUri } from './identity.js'; 4 + 5 + /** 6 + * Yesterday's { players, solvers, sampled } for one language. `players` is the 7 + * cheap distinct-DID count; `solvers` is counted from the sampled records 8 + * (≤ SOLVER_SAMPLE_CAP) to stay under the free-plan subrequest budget. 9 + */ 10 + export async function yesterdayCounts(lang: Lang, yesterdayN: number): Promise<YesterdayCounts> { 11 + const [players, backlinks] = await Promise.all([ 12 + dailyPlayerCount(lang, yesterdayN), 13 + dailyResultBacklinks(lang, yesterdayN, SOLVER_SAMPLE_CAP), 14 + ]); 15 + 16 + let solvers = 0; 17 + await Promise.all( 18 + backlinks.map(async (bl) => { 19 + const rec = await getRecordByUri<ResultRecord>(backlinkUri(bl)); 20 + if (rec && rec.lang === lang && rec.puzzleNumber === yesterdayN && rec.solved) solvers++; 21 + }), 22 + ); 23 + 24 + return { players, solvers, sampled: backlinks.length >= SOLVER_SAMPLE_CAP }; 25 + }
+16
src/facets.ts
··· 1 + /** A Bluesky richtext link facet (UTF-8 byte offsets, per the lexicon). */ 2 + export interface FacetLink { 3 + index: { byteStart: number; byteEnd: number }; 4 + features: Array<{ $type: 'app.bsky.richtext.facet#link'; uri: string }>; 5 + } 6 + 7 + const encoder = new TextEncoder(); 8 + 9 + /** Facet over the first occurrence of `url` in `text`; [] if it's not there. */ 10 + export function linkFacet(text: string, url: string): FacetLink[] { 11 + const charIndex = text.indexOf(url); 12 + if (charIndex === -1) return []; 13 + const byteStart = encoder.encode(text.slice(0, charIndex)).length; 14 + const byteEnd = byteStart + encoder.encode(url).length; 15 + return [{ index: { byteStart, byteEnd }, features: [{ $type: 'app.bsky.richtext.facet#link', uri: url }] }]; 16 + }
+52
src/identity.ts
··· 1 + /** 2 + * Identity resolution (handle/DID -> PDS) and per-PDS record reads. 3 + * Mirrors the app's read path; failures resolve to null and drop the entry. 4 + */ 5 + import { 6 + CompositeDidDocumentResolver, 7 + LocalActorResolver, 8 + PlcDidDocumentResolver, 9 + WebDidDocumentResolver, 10 + XrpcHandleResolver, 11 + type ResolvedActor, 12 + } from '@atcute/identity-resolver'; 13 + import { Client, simpleFetchHandler } from '@atcute/client'; 14 + import { APPVIEW_URL } from './config.js'; 15 + 16 + const actorResolver = new LocalActorResolver({ 17 + handleResolver: new XrpcHandleResolver({ serviceUrl: APPVIEW_URL }), 18 + didDocumentResolver: new CompositeDidDocumentResolver({ 19 + methods: { plc: new PlcDidDocumentResolver(), web: new WebDidDocumentResolver() }, 20 + }), 21 + }); 22 + 23 + /** Resolve a handle or DID to { did, handle, pds }. May reject. */ 24 + export function resolveActor(identifier: string): Promise<ResolvedActor> { 25 + return actorResolver.resolve(identifier as Parameters<typeof actorResolver.resolve>[0]); 26 + } 27 + 28 + /** Unauthenticated read client pointed at a specific PDS. */ 29 + export function pdsClient(pds: string): Client { 30 + return new Client({ handler: simpleFetchHandler({ service: pds }) }); 31 + } 32 + 33 + /** Public read of a record by at:// URI; null on any failure. */ 34 + export async function getRecordByUri<T = unknown>(uri: string): Promise<T | null> { 35 + const m = /^at:\/\/([^/]+)\/([^/]+)\/([^/]+)$/.exec(uri); 36 + if (!m) return null; 37 + try { 38 + const actor = await resolveActor(m[1]!); 39 + const rpc = pdsClient(actor.pds); 40 + const res = await rpc.get('com.atproto.repo.getRecord', { 41 + params: { 42 + repo: m[1]! as `did:${string}:${string}`, 43 + collection: m[2]! as `${string}.${string}.${string}`, 44 + rkey: m[3]!, 45 + }, 46 + }); 47 + if (!res.ok) return null; 48 + return res.data.value as unknown as T; 49 + } catch { 50 + return null; 51 + } 52 + }
+45
src/index.ts
··· 1 + import { puzzleNumberFor, type Lang } from './config.js'; 2 + import { yesterdayCounts } from './counts.js'; 3 + import { composePost, postMarker } from './compose.js'; 4 + import { createBotSession, alreadyPosted, publishPost } from './post.js'; 5 + 6 + export interface Env { 7 + ATMOT_BOT_IDENTIFIER: string; 8 + ATMOT_BOT_APP_PASSWORD: string; 9 + /** When "1", compose and log the post but do not authenticate or publish. */ 10 + DRY_RUN?: string; 11 + } 12 + 13 + /** Map the firing cron expression to a language (FR runs one minute after EN). */ 14 + function langForCron(cron: string): Lang { 15 + return cron.startsWith('11 ') ? 'fr' : 'en'; 16 + } 17 + 18 + export default { 19 + async scheduled(controller: ScheduledController, env: Env, _ctx: ExecutionContext): Promise<void> { 20 + const lang = langForCron(controller.cron); 21 + const todayN = puzzleNumberFor(); 22 + const yesterdayN = todayN - 1; 23 + 24 + try { 25 + const yesterday = yesterdayN >= 1 ? await yesterdayCounts(lang, yesterdayN) : null; 26 + const post = composePost({ lang, todayN, yesterday }); 27 + 28 + if (env.DRY_RUN === '1') { 29 + console.log(`[atmot-bot] DRY_RUN ${lang} #${todayN}:\n${post.text}`); 30 + return; 31 + } 32 + 33 + const session = await createBotSession(env.ATMOT_BOT_IDENTIFIER, env.ATMOT_BOT_APP_PASSWORD); 34 + if (await alreadyPosted(session, postMarker(lang, todayN))) { 35 + console.log(`[atmot-bot] ${lang} #${todayN} already posted; skipping`); 36 + return; 37 + } 38 + const uri = await publishPost(session, post); 39 + console.log(`[atmot-bot] posted ${lang} #${todayN}: ${uri}`); 40 + } catch (err) { 41 + console.error(`[atmot-bot] ${lang} #${todayN} failed:`, err); 42 + throw err; // surface the failure to Cloudflare's cron logs 43 + } 44 + }, 45 + };
+68
src/post.ts
··· 1 + /** 2 + * Posting side: create an app-password session against the bot's PDS, check we 3 + * haven't already posted today (by scanning our own recent posts — stateless), 4 + * and publish the post. 5 + */ 6 + import { Client, simpleFetchHandler, type FetchHandler } from '@atcute/client'; 7 + import { resolveActor } from './identity.js'; 8 + import type { ComposedPost } from './compose.js'; 9 + 10 + export interface BotSession { 11 + client: Client; 12 + did: string; 13 + } 14 + 15 + /** Log in with an app password; returns a client whose requests carry the bearer token. */ 16 + export async function createBotSession(identifier: string, appPassword: string): Promise<BotSession> { 17 + const actor = await resolveActor(identifier); 18 + const pds = actor.pds; 19 + 20 + const loginClient = new Client({ handler: simpleFetchHandler({ service: pds }) }); 21 + const res = await loginClient.post('com.atproto.server.createSession', { 22 + input: { identifier, password: appPassword }, 23 + }); 24 + if (!res.ok) throw new Error(`createSession failed: ${JSON.stringify(res.data)}`); 25 + 26 + const accessJwt = res.data.accessJwt; 27 + const base = simpleFetchHandler({ service: pds }); 28 + const authed: FetchHandler = (pathname, init) => { 29 + const headers = new Headers(init.headers ?? {}); 30 + headers.set('authorization', `Bearer ${accessJwt}`); 31 + return base(pathname, { ...init, headers }); 32 + }; 33 + 34 + return { client: new Client({ handler: authed }), did: res.data.did }; 35 + } 36 + 37 + /** True if any of the bot's recent posts already contains `marker` (today's puzzle token). */ 38 + export async function alreadyPosted(session: BotSession, marker: string): Promise<boolean> { 39 + const res = await session.client.get('com.atproto.repo.listRecords', { 40 + params: { 41 + repo: session.did as `did:${string}:${string}`, 42 + collection: 'app.bsky.feed.post' as `${string}.${string}.${string}`, 43 + limit: 20, 44 + }, 45 + }); 46 + if (!res.ok) return false; // can't confirm — allow the post rather than skip forever 47 + const records = res.data.records as Array<{ value?: { text?: string } }>; 48 + return records.some((r) => typeof r.value?.text === 'string' && r.value.text.includes(marker)); 49 + } 50 + 51 + /** Create the post; returns its at:// uri. */ 52 + export async function publishPost(session: BotSession, post: ComposedPost): Promise<string> { 53 + const res = await session.client.post('com.atproto.repo.createRecord', { 54 + input: { 55 + repo: session.did as `did:${string}:${string}`, 56 + collection: 'app.bsky.feed.post' as `${string}.${string}.${string}`, 57 + record: { 58 + $type: 'app.bsky.feed.post', 59 + text: post.text, 60 + langs: post.langs, 61 + facets: post.facets, 62 + createdAt: new Date().toISOString(), 63 + } as unknown as Record<string, unknown>, 64 + }, 65 + }); 66 + if (!res.ok) throw new Error(`createRecord failed: ${JSON.stringify(res.data)}`); 67 + return res.data.uri; 68 + }
+112
test/compose.test.ts
··· 1 + import { describe, it, expect } from 'vitest'; 2 + import { composePost, postMarker } from '../src/compose.js'; 3 + import { ORIGIN, type YesterdayCounts } from '../src/config.js'; 4 + 5 + const y = (players: number | null, solvers: number, sampled = false): YesterdayCounts => ({ 6 + players, 7 + solvers, 8 + sampled, 9 + }); 10 + 11 + describe('postMarker', () => { 12 + it('is language-specific and carries the puzzle number', () => { 13 + expect(postMarker('en', 5)).toBe('AT Mot #5'); 14 + expect(postMarker('fr', 5)).toBe('AT Mot n°5'); 15 + }); 16 + }); 17 + 18 + describe('composePost — EN', () => { 19 + it('normal: solvers + non-solvers', () => { 20 + const { text, langs, facets } = composePost({ lang: 'en', todayN: 5, yesterday: y(33, 25) }); 21 + expect(text).toBe( 22 + `🟩 AT Mot #5 is live! Six tries to guess today's five-letter word.\n` + 23 + `Congrats to the 25 of you who cracked yesterday's word. And to the 8 who didn't, better luck today!\n` + 24 + `Play: ${ORIGIN}\n` + 25 + `#WordGame #atproto`, 26 + ); 27 + expect(langs).toEqual(['en']); 28 + expect(facets).toHaveLength(1); 29 + expect(facets[0]!.features[0]!.uri).toBe(ORIGIN); 30 + }); 31 + 32 + it('singular solver, no non-solvers', () => { 33 + const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(1, 1) }); 34 + expect(text).toContain('Congrats to the one player who cracked yesterday\'s word.'); 35 + expect(text).not.toContain('who didn\'t'); 36 + }); 37 + 38 + it('singular non-solver', () => { 39 + const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(5, 4) }); 40 + expect(text).toContain('And to the one who didn\'t, better luck today!'); 41 + }); 42 + 43 + it('zero solvers', () => { 44 + const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(5, 0) }); 45 + expect(text).toContain('Nobody cracked yesterday\'s word. Fresh start today!'); 46 + }); 47 + 48 + it('everyone solved (no non-solver clause)', () => { 49 + const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(10, 10) }); 50 + expect(text).toContain('Congrats to the 10 of you who cracked yesterday\'s word.'); 51 + expect(text).not.toContain('who didn\'t'); 52 + }); 53 + 54 + it('sampled: hedged floor, no exact non-solver count', () => { 55 + const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(50, 20, true) }); 56 + expect(text).toContain('Congrats to the 20+ of you who cracked yesterday\'s word. Can you join them today?'); 57 + }); 58 + 59 + it('day 1 (no yesterday): invite only', () => { 60 + const { text } = composePost({ lang: 'en', todayN: 1, yesterday: null }); 61 + expect(text).toBe( 62 + `🟩 AT Mot #1 is live! Six tries to guess today's five-letter word.\n` + 63 + `Play: ${ORIGIN}\n` + 64 + `#WordGame #atproto`, 65 + ); 66 + }); 67 + 68 + it('Constellation unreachable (players null): no congrats', () => { 69 + const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(null, 0) }); 70 + expect(text).not.toContain('Congrats'); 71 + expect(text).not.toContain('Nobody'); 72 + }); 73 + 74 + it('nobody played yesterday (players 0): no congrats', () => { 75 + const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(0, 0) }); 76 + expect(text).not.toContain('Congrats'); 77 + expect(text).not.toContain('Nobody'); 78 + }); 79 + }); 80 + 81 + describe('composePost — FR', () => { 82 + it('normal: solvers + non-solvers', () => { 83 + const { text, langs } = composePost({ lang: 'fr', todayN: 5, yesterday: y(23, 18) }); 84 + expect(text).toBe( 85 + `🟩 AT Mot n°5 est en ligne ! Six essais pour deviner le mot de cinq lettres du jour.\n` + 86 + `Bravo aux 18 qui ont trouvé le mot d'hier. Et aux 5 qui ont séché, meilleure chance aujourd'hui !\n` + 87 + `Jouez : ${ORIGIN}\n` + 88 + `#JeuDeMots #atproto`, 89 + ); 90 + expect(langs).toEqual(['fr']); 91 + }); 92 + 93 + it('singular solver uses "au seul joueur" + "a trouvé"', () => { 94 + const { text } = composePost({ lang: 'fr', todayN: 5, yesterday: y(1, 1) }); 95 + expect(text).toContain('Bravo au seul joueur qui a trouvé le mot d\'hier.'); 96 + }); 97 + 98 + it('singular non-solver', () => { 99 + const { text } = composePost({ lang: 'fr', todayN: 5, yesterday: y(5, 4) }); 100 + expect(text).toContain('Et au seul qui a séché, meilleure chance aujourd\'hui !'); 101 + }); 102 + 103 + it('zero solvers', () => { 104 + const { text } = composePost({ lang: 'fr', todayN: 5, yesterday: y(5, 0) }); 105 + expect(text).toContain('Personne n\'a trouvé le mot d\'hier. Nouveau départ aujourd\'hui !'); 106 + }); 107 + 108 + it('sampled hedge', () => { 109 + const { text } = composePost({ lang: 'fr', todayN: 5, yesterday: y(50, 20, true) }); 110 + expect(text).toContain('Bravo aux 20+ qui ont trouvé le mot d\'hier. Saurez-vous les rejoindre aujourd\'hui ?'); 111 + }); 112 + });
+29
test/config.test.ts
··· 1 + import { describe, it, expect } from 'vitest'; 2 + import { EPOCH_UTC_MS, puzzleNumberFor, daysSinceEpoch, puzzleTarget } from '../src/config.js'; 3 + 4 + describe('puzzle numbering', () => { 5 + it('epoch day is puzzle #1', () => { 6 + expect(puzzleNumberFor(EPOCH_UTC_MS)).toBe(1); 7 + expect(daysSinceEpoch(EPOCH_UTC_MS)).toBe(0); 8 + }); 9 + 10 + it('four days after epoch is puzzle #5', () => { 11 + expect(puzzleNumberFor(Date.UTC(2026, 5, 27))).toBe(5); 12 + }); 13 + 14 + it('only changes at UTC midnight, not mid-day', () => { 15 + const lateInEpochDay = EPOCH_UTC_MS + 23 * 3_600_000; 16 + expect(puzzleNumberFor(lateInEpochDay)).toBe(1); 17 + }); 18 + 19 + it('days before the epoch are < 1 (no puzzle yet)', () => { 20 + expect(puzzleNumberFor(Date.UTC(2026, 5, 22))).toBe(0); 21 + }); 22 + }); 23 + 24 + describe('puzzleTarget format (frozen)', () => { 25 + it('builds the canonical permalink', () => { 26 + expect(puzzleTarget('en', 5)).toBe('https://atmot.herve.bzh/p/en/5'); 27 + expect(puzzleTarget('fr', 12)).toBe('https://atmot.herve.bzh/p/fr/12'); 28 + }); 29 + });
+30
test/facets.test.ts
··· 1 + import { describe, it, expect } from 'vitest'; 2 + import { linkFacet } from '../src/facets.js'; 3 + 4 + const URL = 'https://atmot.herve.bzh'; // 23 bytes (ASCII) 5 + 6 + describe('linkFacet', () => { 7 + it('computes ASCII byte offsets', () => { 8 + const text = `Play: ${URL}`; 9 + expect(linkFacet(text, URL)).toEqual([ 10 + { 11 + index: { byteStart: 6, byteEnd: 29 }, 12 + features: [{ $type: 'app.bsky.richtext.facet#link', uri: URL }], 13 + }, 14 + ]); 15 + }); 16 + 17 + it('counts multi-byte characters before the url (🟩 is 4 bytes)', () => { 18 + const text = `🟩 ${URL}`; 19 + expect(linkFacet(text, URL)).toEqual([ 20 + { 21 + index: { byteStart: 5, byteEnd: 28 }, 22 + features: [{ $type: 'app.bsky.richtext.facet#link', uri: URL }], 23 + }, 24 + ]); 25 + }); 26 + 27 + it('returns [] when the url is not present', () => { 28 + expect(linkFacet('no link here', URL)).toEqual([]); 29 + }); 30 + });
+16
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "ESNext", 5 + "moduleResolution": "Bundler", 6 + "lib": ["ES2022"], 7 + "types": ["@cloudflare/workers-types", "@atcute/atproto"], 8 + "strict": true, 9 + "noEmit": true, 10 + "skipLibCheck": true, 11 + "esModuleInterop": true, 12 + "verbatimModuleSyntax": true, 13 + "noUncheckedIndexedAccess": true 14 + }, 15 + "include": ["src", "test"] 16 + }
+6
wrangler.toml
··· 1 + name = "atmot-bot" 2 + main = "src/index.ts" 3 + compatibility_date = "2026-06-25" 4 + 5 + [triggers] 6 + crons = ["10 0 * * *", "11 0 * * *"]