Monorepo for Tangled
0
fork

Configure Feed

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

jobs: worker for jobs at tangled

Like actual jobs. Describe job postings as markdown under postings/.
Creates new application submissions as issues in Linear. Available at
jobs.tangled.org.

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

authored by

Anirudh Oppiliappan and committed by
Tangled
a073d29e 3ff418da

+2225
+3
jobs/.gitignore
··· 1 + node_modules/ 2 + .dev.vars 3 + .wrangler/
+1552
jobs/package-lock.json
··· 1 + { 2 + "name": "tangled-jobs", 3 + "version": "1.0.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "tangled-jobs", 9 + "version": "1.0.0", 10 + "dependencies": { 11 + "countries-list": "^3.1.1", 12 + "marked": "^15.0.0" 13 + }, 14 + "devDependencies": { 15 + "@cloudflare/workers-types": "^4.20250428.0", 16 + "typescript": "^5.8.3", 17 + "wrangler": "^4.13.2" 18 + } 19 + }, 20 + "node_modules/@cloudflare/kv-asset-handler": { 21 + "version": "0.4.2", 22 + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz", 23 + "integrity": "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==", 24 + "dev": true, 25 + "license": "MIT OR Apache-2.0", 26 + "engines": { 27 + "node": ">=18.0.0" 28 + } 29 + }, 30 + "node_modules/@cloudflare/unenv-preset": { 31 + "version": "2.16.1", 32 + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.1.tgz", 33 + "integrity": "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==", 34 + "dev": true, 35 + "license": "MIT OR Apache-2.0", 36 + "peerDependencies": { 37 + "unenv": "2.0.0-rc.24", 38 + "workerd": ">1.20260305.0 <2.0.0-0" 39 + }, 40 + "peerDependenciesMeta": { 41 + "workerd": { 42 + "optional": true 43 + } 44 + } 45 + }, 46 + "node_modules/@cloudflare/workerd-darwin-64": { 47 + "version": "1.20260426.1", 48 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260426.1.tgz", 49 + "integrity": "sha512-Ch7DqsmYzSQRTY87pZpsGsFVz9VVBnLPnCBOHxKt1HH25a7oMu1w1PbPWqVmE0VerCLsj/TScX7Ob3v6E14TZw==", 50 + "cpu": [ 51 + "x64" 52 + ], 53 + "dev": true, 54 + "license": "Apache-2.0", 55 + "optional": true, 56 + "os": [ 57 + "darwin" 58 + ], 59 + "engines": { 60 + "node": ">=16" 61 + } 62 + }, 63 + "node_modules/@cloudflare/workerd-darwin-arm64": { 64 + "version": "1.20260426.1", 65 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260426.1.tgz", 66 + "integrity": "sha512-0m0U8vaPRH25SpKjbSyRql6gmPe4rCsETRV2WW0qBnuMdKNr5Vh5/Uez80xVrfiCCRMTULGeg63Nqg2vg6CDOA==", 67 + "cpu": [ 68 + "arm64" 69 + ], 70 + "dev": true, 71 + "license": "Apache-2.0", 72 + "optional": true, 73 + "os": [ 74 + "darwin" 75 + ], 76 + "engines": { 77 + "node": ">=16" 78 + } 79 + }, 80 + "node_modules/@cloudflare/workerd-linux-64": { 81 + "version": "1.20260426.1", 82 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260426.1.tgz", 83 + "integrity": "sha512-C8LlC8uSYzg49y51n++75esxZmMp+Uz1OKHHA/4lkv6rjOTbcHQJuEwSLppjybVIXpv7A8MBhbu9iyCTvyv1mw==", 84 + "cpu": [ 85 + "x64" 86 + ], 87 + "dev": true, 88 + "license": "Apache-2.0", 89 + "optional": true, 90 + "os": [ 91 + "linux" 92 + ], 93 + "engines": { 94 + "node": ">=16" 95 + } 96 + }, 97 + "node_modules/@cloudflare/workerd-linux-arm64": { 98 + "version": "1.20260426.1", 99 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260426.1.tgz", 100 + "integrity": "sha512-ESVp/OIFMAqjQsa8BOP2BQQz5Vpfv6ncN6lNnIuNeOgsISQBdYk+LA60bwQHMud9tvmnSYtONp1zkZ8OQz+x6w==", 101 + "cpu": [ 102 + "arm64" 103 + ], 104 + "dev": true, 105 + "license": "Apache-2.0", 106 + "optional": true, 107 + "os": [ 108 + "linux" 109 + ], 110 + "engines": { 111 + "node": ">=16" 112 + } 113 + }, 114 + "node_modules/@cloudflare/workerd-windows-64": { 115 + "version": "1.20260426.1", 116 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260426.1.tgz", 117 + "integrity": "sha512-d3Xj/IjINRgNVwH+eKhpUn4xkkcEewbWXbOvBlapiirKWh5zl9m0Epi3qOqmjyRYK6MICqIGXg4qZBEt0lxudw==", 118 + "cpu": [ 119 + "x64" 120 + ], 121 + "dev": true, 122 + "license": "Apache-2.0", 123 + "optional": true, 124 + "os": [ 125 + "win32" 126 + ], 127 + "engines": { 128 + "node": ">=16" 129 + } 130 + }, 131 + "node_modules/@cloudflare/workers-types": { 132 + "version": "4.20260426.1", 133 + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260426.1.tgz", 134 + "integrity": "sha512-cBYeQaWwv/jFV8ualmwp6wIxmAf0rDe2DPPQwPbslKmPHqgv861YpAvm45r05K40QboZgxNQVIPgNkmtHqZeJQ==", 135 + "dev": true, 136 + "license": "MIT OR Apache-2.0", 137 + "peer": true 138 + }, 139 + "node_modules/@cspotcode/source-map-support": { 140 + "version": "0.8.1", 141 + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 142 + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 143 + "dev": true, 144 + "license": "MIT", 145 + "dependencies": { 146 + "@jridgewell/trace-mapping": "0.3.9" 147 + }, 148 + "engines": { 149 + "node": ">=12" 150 + } 151 + }, 152 + "node_modules/@emnapi/runtime": { 153 + "version": "1.10.0", 154 + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", 155 + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", 156 + "dev": true, 157 + "license": "MIT", 158 + "optional": true, 159 + "dependencies": { 160 + "tslib": "^2.4.0" 161 + } 162 + }, 163 + "node_modules/@esbuild/aix-ppc64": { 164 + "version": "0.27.3", 165 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", 166 + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", 167 + "cpu": [ 168 + "ppc64" 169 + ], 170 + "dev": true, 171 + "license": "MIT", 172 + "optional": true, 173 + "os": [ 174 + "aix" 175 + ], 176 + "engines": { 177 + "node": ">=18" 178 + } 179 + }, 180 + "node_modules/@esbuild/android-arm": { 181 + "version": "0.27.3", 182 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", 183 + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", 184 + "cpu": [ 185 + "arm" 186 + ], 187 + "dev": true, 188 + "license": "MIT", 189 + "optional": true, 190 + "os": [ 191 + "android" 192 + ], 193 + "engines": { 194 + "node": ">=18" 195 + } 196 + }, 197 + "node_modules/@esbuild/android-arm64": { 198 + "version": "0.27.3", 199 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", 200 + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", 201 + "cpu": [ 202 + "arm64" 203 + ], 204 + "dev": true, 205 + "license": "MIT", 206 + "optional": true, 207 + "os": [ 208 + "android" 209 + ], 210 + "engines": { 211 + "node": ">=18" 212 + } 213 + }, 214 + "node_modules/@esbuild/android-x64": { 215 + "version": "0.27.3", 216 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", 217 + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", 218 + "cpu": [ 219 + "x64" 220 + ], 221 + "dev": true, 222 + "license": "MIT", 223 + "optional": true, 224 + "os": [ 225 + "android" 226 + ], 227 + "engines": { 228 + "node": ">=18" 229 + } 230 + }, 231 + "node_modules/@esbuild/darwin-arm64": { 232 + "version": "0.27.3", 233 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", 234 + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", 235 + "cpu": [ 236 + "arm64" 237 + ], 238 + "dev": true, 239 + "license": "MIT", 240 + "optional": true, 241 + "os": [ 242 + "darwin" 243 + ], 244 + "engines": { 245 + "node": ">=18" 246 + } 247 + }, 248 + "node_modules/@esbuild/darwin-x64": { 249 + "version": "0.27.3", 250 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", 251 + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", 252 + "cpu": [ 253 + "x64" 254 + ], 255 + "dev": true, 256 + "license": "MIT", 257 + "optional": true, 258 + "os": [ 259 + "darwin" 260 + ], 261 + "engines": { 262 + "node": ">=18" 263 + } 264 + }, 265 + "node_modules/@esbuild/freebsd-arm64": { 266 + "version": "0.27.3", 267 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", 268 + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", 269 + "cpu": [ 270 + "arm64" 271 + ], 272 + "dev": true, 273 + "license": "MIT", 274 + "optional": true, 275 + "os": [ 276 + "freebsd" 277 + ], 278 + "engines": { 279 + "node": ">=18" 280 + } 281 + }, 282 + "node_modules/@esbuild/freebsd-x64": { 283 + "version": "0.27.3", 284 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", 285 + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", 286 + "cpu": [ 287 + "x64" 288 + ], 289 + "dev": true, 290 + "license": "MIT", 291 + "optional": true, 292 + "os": [ 293 + "freebsd" 294 + ], 295 + "engines": { 296 + "node": ">=18" 297 + } 298 + }, 299 + "node_modules/@esbuild/linux-arm": { 300 + "version": "0.27.3", 301 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", 302 + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", 303 + "cpu": [ 304 + "arm" 305 + ], 306 + "dev": true, 307 + "license": "MIT", 308 + "optional": true, 309 + "os": [ 310 + "linux" 311 + ], 312 + "engines": { 313 + "node": ">=18" 314 + } 315 + }, 316 + "node_modules/@esbuild/linux-arm64": { 317 + "version": "0.27.3", 318 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", 319 + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", 320 + "cpu": [ 321 + "arm64" 322 + ], 323 + "dev": true, 324 + "license": "MIT", 325 + "optional": true, 326 + "os": [ 327 + "linux" 328 + ], 329 + "engines": { 330 + "node": ">=18" 331 + } 332 + }, 333 + "node_modules/@esbuild/linux-ia32": { 334 + "version": "0.27.3", 335 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", 336 + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", 337 + "cpu": [ 338 + "ia32" 339 + ], 340 + "dev": true, 341 + "license": "MIT", 342 + "optional": true, 343 + "os": [ 344 + "linux" 345 + ], 346 + "engines": { 347 + "node": ">=18" 348 + } 349 + }, 350 + "node_modules/@esbuild/linux-loong64": { 351 + "version": "0.27.3", 352 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", 353 + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", 354 + "cpu": [ 355 + "loong64" 356 + ], 357 + "dev": true, 358 + "license": "MIT", 359 + "optional": true, 360 + "os": [ 361 + "linux" 362 + ], 363 + "engines": { 364 + "node": ">=18" 365 + } 366 + }, 367 + "node_modules/@esbuild/linux-mips64el": { 368 + "version": "0.27.3", 369 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", 370 + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", 371 + "cpu": [ 372 + "mips64el" 373 + ], 374 + "dev": true, 375 + "license": "MIT", 376 + "optional": true, 377 + "os": [ 378 + "linux" 379 + ], 380 + "engines": { 381 + "node": ">=18" 382 + } 383 + }, 384 + "node_modules/@esbuild/linux-ppc64": { 385 + "version": "0.27.3", 386 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", 387 + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", 388 + "cpu": [ 389 + "ppc64" 390 + ], 391 + "dev": true, 392 + "license": "MIT", 393 + "optional": true, 394 + "os": [ 395 + "linux" 396 + ], 397 + "engines": { 398 + "node": ">=18" 399 + } 400 + }, 401 + "node_modules/@esbuild/linux-riscv64": { 402 + "version": "0.27.3", 403 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", 404 + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", 405 + "cpu": [ 406 + "riscv64" 407 + ], 408 + "dev": true, 409 + "license": "MIT", 410 + "optional": true, 411 + "os": [ 412 + "linux" 413 + ], 414 + "engines": { 415 + "node": ">=18" 416 + } 417 + }, 418 + "node_modules/@esbuild/linux-s390x": { 419 + "version": "0.27.3", 420 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", 421 + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", 422 + "cpu": [ 423 + "s390x" 424 + ], 425 + "dev": true, 426 + "license": "MIT", 427 + "optional": true, 428 + "os": [ 429 + "linux" 430 + ], 431 + "engines": { 432 + "node": ">=18" 433 + } 434 + }, 435 + "node_modules/@esbuild/linux-x64": { 436 + "version": "0.27.3", 437 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", 438 + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", 439 + "cpu": [ 440 + "x64" 441 + ], 442 + "dev": true, 443 + "license": "MIT", 444 + "optional": true, 445 + "os": [ 446 + "linux" 447 + ], 448 + "engines": { 449 + "node": ">=18" 450 + } 451 + }, 452 + "node_modules/@esbuild/netbsd-arm64": { 453 + "version": "0.27.3", 454 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", 455 + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", 456 + "cpu": [ 457 + "arm64" 458 + ], 459 + "dev": true, 460 + "license": "MIT", 461 + "optional": true, 462 + "os": [ 463 + "netbsd" 464 + ], 465 + "engines": { 466 + "node": ">=18" 467 + } 468 + }, 469 + "node_modules/@esbuild/netbsd-x64": { 470 + "version": "0.27.3", 471 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", 472 + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", 473 + "cpu": [ 474 + "x64" 475 + ], 476 + "dev": true, 477 + "license": "MIT", 478 + "optional": true, 479 + "os": [ 480 + "netbsd" 481 + ], 482 + "engines": { 483 + "node": ">=18" 484 + } 485 + }, 486 + "node_modules/@esbuild/openbsd-arm64": { 487 + "version": "0.27.3", 488 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", 489 + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", 490 + "cpu": [ 491 + "arm64" 492 + ], 493 + "dev": true, 494 + "license": "MIT", 495 + "optional": true, 496 + "os": [ 497 + "openbsd" 498 + ], 499 + "engines": { 500 + "node": ">=18" 501 + } 502 + }, 503 + "node_modules/@esbuild/openbsd-x64": { 504 + "version": "0.27.3", 505 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", 506 + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", 507 + "cpu": [ 508 + "x64" 509 + ], 510 + "dev": true, 511 + "license": "MIT", 512 + "optional": true, 513 + "os": [ 514 + "openbsd" 515 + ], 516 + "engines": { 517 + "node": ">=18" 518 + } 519 + }, 520 + "node_modules/@esbuild/openharmony-arm64": { 521 + "version": "0.27.3", 522 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", 523 + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", 524 + "cpu": [ 525 + "arm64" 526 + ], 527 + "dev": true, 528 + "license": "MIT", 529 + "optional": true, 530 + "os": [ 531 + "openharmony" 532 + ], 533 + "engines": { 534 + "node": ">=18" 535 + } 536 + }, 537 + "node_modules/@esbuild/sunos-x64": { 538 + "version": "0.27.3", 539 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", 540 + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", 541 + "cpu": [ 542 + "x64" 543 + ], 544 + "dev": true, 545 + "license": "MIT", 546 + "optional": true, 547 + "os": [ 548 + "sunos" 549 + ], 550 + "engines": { 551 + "node": ">=18" 552 + } 553 + }, 554 + "node_modules/@esbuild/win32-arm64": { 555 + "version": "0.27.3", 556 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", 557 + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", 558 + "cpu": [ 559 + "arm64" 560 + ], 561 + "dev": true, 562 + "license": "MIT", 563 + "optional": true, 564 + "os": [ 565 + "win32" 566 + ], 567 + "engines": { 568 + "node": ">=18" 569 + } 570 + }, 571 + "node_modules/@esbuild/win32-ia32": { 572 + "version": "0.27.3", 573 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", 574 + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", 575 + "cpu": [ 576 + "ia32" 577 + ], 578 + "dev": true, 579 + "license": "MIT", 580 + "optional": true, 581 + "os": [ 582 + "win32" 583 + ], 584 + "engines": { 585 + "node": ">=18" 586 + } 587 + }, 588 + "node_modules/@esbuild/win32-x64": { 589 + "version": "0.27.3", 590 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", 591 + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", 592 + "cpu": [ 593 + "x64" 594 + ], 595 + "dev": true, 596 + "license": "MIT", 597 + "optional": true, 598 + "os": [ 599 + "win32" 600 + ], 601 + "engines": { 602 + "node": ">=18" 603 + } 604 + }, 605 + "node_modules/@img/colour": { 606 + "version": "1.1.0", 607 + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", 608 + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", 609 + "dev": true, 610 + "license": "MIT", 611 + "engines": { 612 + "node": ">=18" 613 + } 614 + }, 615 + "node_modules/@img/sharp-darwin-arm64": { 616 + "version": "0.34.5", 617 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", 618 + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", 619 + "cpu": [ 620 + "arm64" 621 + ], 622 + "dev": true, 623 + "license": "Apache-2.0", 624 + "optional": true, 625 + "os": [ 626 + "darwin" 627 + ], 628 + "engines": { 629 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 630 + }, 631 + "funding": { 632 + "url": "https://opencollective.com/libvips" 633 + }, 634 + "optionalDependencies": { 635 + "@img/sharp-libvips-darwin-arm64": "1.2.4" 636 + } 637 + }, 638 + "node_modules/@img/sharp-darwin-x64": { 639 + "version": "0.34.5", 640 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", 641 + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", 642 + "cpu": [ 643 + "x64" 644 + ], 645 + "dev": true, 646 + "license": "Apache-2.0", 647 + "optional": true, 648 + "os": [ 649 + "darwin" 650 + ], 651 + "engines": { 652 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 653 + }, 654 + "funding": { 655 + "url": "https://opencollective.com/libvips" 656 + }, 657 + "optionalDependencies": { 658 + "@img/sharp-libvips-darwin-x64": "1.2.4" 659 + } 660 + }, 661 + "node_modules/@img/sharp-libvips-darwin-arm64": { 662 + "version": "1.2.4", 663 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", 664 + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", 665 + "cpu": [ 666 + "arm64" 667 + ], 668 + "dev": true, 669 + "license": "LGPL-3.0-or-later", 670 + "optional": true, 671 + "os": [ 672 + "darwin" 673 + ], 674 + "funding": { 675 + "url": "https://opencollective.com/libvips" 676 + } 677 + }, 678 + "node_modules/@img/sharp-libvips-darwin-x64": { 679 + "version": "1.2.4", 680 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", 681 + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", 682 + "cpu": [ 683 + "x64" 684 + ], 685 + "dev": true, 686 + "license": "LGPL-3.0-or-later", 687 + "optional": true, 688 + "os": [ 689 + "darwin" 690 + ], 691 + "funding": { 692 + "url": "https://opencollective.com/libvips" 693 + } 694 + }, 695 + "node_modules/@img/sharp-libvips-linux-arm": { 696 + "version": "1.2.4", 697 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", 698 + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", 699 + "cpu": [ 700 + "arm" 701 + ], 702 + "dev": true, 703 + "license": "LGPL-3.0-or-later", 704 + "optional": true, 705 + "os": [ 706 + "linux" 707 + ], 708 + "funding": { 709 + "url": "https://opencollective.com/libvips" 710 + } 711 + }, 712 + "node_modules/@img/sharp-libvips-linux-arm64": { 713 + "version": "1.2.4", 714 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", 715 + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", 716 + "cpu": [ 717 + "arm64" 718 + ], 719 + "dev": true, 720 + "license": "LGPL-3.0-or-later", 721 + "optional": true, 722 + "os": [ 723 + "linux" 724 + ], 725 + "funding": { 726 + "url": "https://opencollective.com/libvips" 727 + } 728 + }, 729 + "node_modules/@img/sharp-libvips-linux-ppc64": { 730 + "version": "1.2.4", 731 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", 732 + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", 733 + "cpu": [ 734 + "ppc64" 735 + ], 736 + "dev": true, 737 + "license": "LGPL-3.0-or-later", 738 + "optional": true, 739 + "os": [ 740 + "linux" 741 + ], 742 + "funding": { 743 + "url": "https://opencollective.com/libvips" 744 + } 745 + }, 746 + "node_modules/@img/sharp-libvips-linux-riscv64": { 747 + "version": "1.2.4", 748 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", 749 + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", 750 + "cpu": [ 751 + "riscv64" 752 + ], 753 + "dev": true, 754 + "license": "LGPL-3.0-or-later", 755 + "optional": true, 756 + "os": [ 757 + "linux" 758 + ], 759 + "funding": { 760 + "url": "https://opencollective.com/libvips" 761 + } 762 + }, 763 + "node_modules/@img/sharp-libvips-linux-s390x": { 764 + "version": "1.2.4", 765 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", 766 + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", 767 + "cpu": [ 768 + "s390x" 769 + ], 770 + "dev": true, 771 + "license": "LGPL-3.0-or-later", 772 + "optional": true, 773 + "os": [ 774 + "linux" 775 + ], 776 + "funding": { 777 + "url": "https://opencollective.com/libvips" 778 + } 779 + }, 780 + "node_modules/@img/sharp-libvips-linux-x64": { 781 + "version": "1.2.4", 782 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", 783 + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", 784 + "cpu": [ 785 + "x64" 786 + ], 787 + "dev": true, 788 + "license": "LGPL-3.0-or-later", 789 + "optional": true, 790 + "os": [ 791 + "linux" 792 + ], 793 + "funding": { 794 + "url": "https://opencollective.com/libvips" 795 + } 796 + }, 797 + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { 798 + "version": "1.2.4", 799 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", 800 + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", 801 + "cpu": [ 802 + "arm64" 803 + ], 804 + "dev": true, 805 + "license": "LGPL-3.0-or-later", 806 + "optional": true, 807 + "os": [ 808 + "linux" 809 + ], 810 + "funding": { 811 + "url": "https://opencollective.com/libvips" 812 + } 813 + }, 814 + "node_modules/@img/sharp-libvips-linuxmusl-x64": { 815 + "version": "1.2.4", 816 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", 817 + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", 818 + "cpu": [ 819 + "x64" 820 + ], 821 + "dev": true, 822 + "license": "LGPL-3.0-or-later", 823 + "optional": true, 824 + "os": [ 825 + "linux" 826 + ], 827 + "funding": { 828 + "url": "https://opencollective.com/libvips" 829 + } 830 + }, 831 + "node_modules/@img/sharp-linux-arm": { 832 + "version": "0.34.5", 833 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", 834 + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", 835 + "cpu": [ 836 + "arm" 837 + ], 838 + "dev": true, 839 + "license": "Apache-2.0", 840 + "optional": true, 841 + "os": [ 842 + "linux" 843 + ], 844 + "engines": { 845 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 846 + }, 847 + "funding": { 848 + "url": "https://opencollective.com/libvips" 849 + }, 850 + "optionalDependencies": { 851 + "@img/sharp-libvips-linux-arm": "1.2.4" 852 + } 853 + }, 854 + "node_modules/@img/sharp-linux-arm64": { 855 + "version": "0.34.5", 856 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", 857 + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", 858 + "cpu": [ 859 + "arm64" 860 + ], 861 + "dev": true, 862 + "license": "Apache-2.0", 863 + "optional": true, 864 + "os": [ 865 + "linux" 866 + ], 867 + "engines": { 868 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 869 + }, 870 + "funding": { 871 + "url": "https://opencollective.com/libvips" 872 + }, 873 + "optionalDependencies": { 874 + "@img/sharp-libvips-linux-arm64": "1.2.4" 875 + } 876 + }, 877 + "node_modules/@img/sharp-linux-ppc64": { 878 + "version": "0.34.5", 879 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", 880 + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", 881 + "cpu": [ 882 + "ppc64" 883 + ], 884 + "dev": true, 885 + "license": "Apache-2.0", 886 + "optional": true, 887 + "os": [ 888 + "linux" 889 + ], 890 + "engines": { 891 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 892 + }, 893 + "funding": { 894 + "url": "https://opencollective.com/libvips" 895 + }, 896 + "optionalDependencies": { 897 + "@img/sharp-libvips-linux-ppc64": "1.2.4" 898 + } 899 + }, 900 + "node_modules/@img/sharp-linux-riscv64": { 901 + "version": "0.34.5", 902 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", 903 + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", 904 + "cpu": [ 905 + "riscv64" 906 + ], 907 + "dev": true, 908 + "license": "Apache-2.0", 909 + "optional": true, 910 + "os": [ 911 + "linux" 912 + ], 913 + "engines": { 914 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 915 + }, 916 + "funding": { 917 + "url": "https://opencollective.com/libvips" 918 + }, 919 + "optionalDependencies": { 920 + "@img/sharp-libvips-linux-riscv64": "1.2.4" 921 + } 922 + }, 923 + "node_modules/@img/sharp-linux-s390x": { 924 + "version": "0.34.5", 925 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", 926 + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", 927 + "cpu": [ 928 + "s390x" 929 + ], 930 + "dev": true, 931 + "license": "Apache-2.0", 932 + "optional": true, 933 + "os": [ 934 + "linux" 935 + ], 936 + "engines": { 937 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 938 + }, 939 + "funding": { 940 + "url": "https://opencollective.com/libvips" 941 + }, 942 + "optionalDependencies": { 943 + "@img/sharp-libvips-linux-s390x": "1.2.4" 944 + } 945 + }, 946 + "node_modules/@img/sharp-linux-x64": { 947 + "version": "0.34.5", 948 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", 949 + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", 950 + "cpu": [ 951 + "x64" 952 + ], 953 + "dev": true, 954 + "license": "Apache-2.0", 955 + "optional": true, 956 + "os": [ 957 + "linux" 958 + ], 959 + "engines": { 960 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 961 + }, 962 + "funding": { 963 + "url": "https://opencollective.com/libvips" 964 + }, 965 + "optionalDependencies": { 966 + "@img/sharp-libvips-linux-x64": "1.2.4" 967 + } 968 + }, 969 + "node_modules/@img/sharp-linuxmusl-arm64": { 970 + "version": "0.34.5", 971 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", 972 + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", 973 + "cpu": [ 974 + "arm64" 975 + ], 976 + "dev": true, 977 + "license": "Apache-2.0", 978 + "optional": true, 979 + "os": [ 980 + "linux" 981 + ], 982 + "engines": { 983 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 984 + }, 985 + "funding": { 986 + "url": "https://opencollective.com/libvips" 987 + }, 988 + "optionalDependencies": { 989 + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" 990 + } 991 + }, 992 + "node_modules/@img/sharp-linuxmusl-x64": { 993 + "version": "0.34.5", 994 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", 995 + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", 996 + "cpu": [ 997 + "x64" 998 + ], 999 + "dev": true, 1000 + "license": "Apache-2.0", 1001 + "optional": true, 1002 + "os": [ 1003 + "linux" 1004 + ], 1005 + "engines": { 1006 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1007 + }, 1008 + "funding": { 1009 + "url": "https://opencollective.com/libvips" 1010 + }, 1011 + "optionalDependencies": { 1012 + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" 1013 + } 1014 + }, 1015 + "node_modules/@img/sharp-wasm32": { 1016 + "version": "0.34.5", 1017 + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", 1018 + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", 1019 + "cpu": [ 1020 + "wasm32" 1021 + ], 1022 + "dev": true, 1023 + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", 1024 + "optional": true, 1025 + "dependencies": { 1026 + "@emnapi/runtime": "^1.7.0" 1027 + }, 1028 + "engines": { 1029 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1030 + }, 1031 + "funding": { 1032 + "url": "https://opencollective.com/libvips" 1033 + } 1034 + }, 1035 + "node_modules/@img/sharp-win32-arm64": { 1036 + "version": "0.34.5", 1037 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", 1038 + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", 1039 + "cpu": [ 1040 + "arm64" 1041 + ], 1042 + "dev": true, 1043 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1044 + "optional": true, 1045 + "os": [ 1046 + "win32" 1047 + ], 1048 + "engines": { 1049 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1050 + }, 1051 + "funding": { 1052 + "url": "https://opencollective.com/libvips" 1053 + } 1054 + }, 1055 + "node_modules/@img/sharp-win32-ia32": { 1056 + "version": "0.34.5", 1057 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", 1058 + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", 1059 + "cpu": [ 1060 + "ia32" 1061 + ], 1062 + "dev": true, 1063 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1064 + "optional": true, 1065 + "os": [ 1066 + "win32" 1067 + ], 1068 + "engines": { 1069 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1070 + }, 1071 + "funding": { 1072 + "url": "https://opencollective.com/libvips" 1073 + } 1074 + }, 1075 + "node_modules/@img/sharp-win32-x64": { 1076 + "version": "0.34.5", 1077 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", 1078 + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", 1079 + "cpu": [ 1080 + "x64" 1081 + ], 1082 + "dev": true, 1083 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1084 + "optional": true, 1085 + "os": [ 1086 + "win32" 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 + }, 1095 + "node_modules/@jridgewell/resolve-uri": { 1096 + "version": "3.1.2", 1097 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 1098 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 1099 + "dev": true, 1100 + "license": "MIT", 1101 + "engines": { 1102 + "node": ">=6.0.0" 1103 + } 1104 + }, 1105 + "node_modules/@jridgewell/sourcemap-codec": { 1106 + "version": "1.5.5", 1107 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", 1108 + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", 1109 + "dev": true, 1110 + "license": "MIT" 1111 + }, 1112 + "node_modules/@jridgewell/trace-mapping": { 1113 + "version": "0.3.9", 1114 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 1115 + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 1116 + "dev": true, 1117 + "license": "MIT", 1118 + "dependencies": { 1119 + "@jridgewell/resolve-uri": "^3.0.3", 1120 + "@jridgewell/sourcemap-codec": "^1.4.10" 1121 + } 1122 + }, 1123 + "node_modules/@poppinss/colors": { 1124 + "version": "4.1.6", 1125 + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", 1126 + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", 1127 + "dev": true, 1128 + "license": "MIT", 1129 + "dependencies": { 1130 + "kleur": "^4.1.5" 1131 + } 1132 + }, 1133 + "node_modules/@poppinss/dumper": { 1134 + "version": "0.6.5", 1135 + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", 1136 + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", 1137 + "dev": true, 1138 + "license": "MIT", 1139 + "dependencies": { 1140 + "@poppinss/colors": "^4.1.5", 1141 + "@sindresorhus/is": "^7.0.2", 1142 + "supports-color": "^10.0.0" 1143 + } 1144 + }, 1145 + "node_modules/@poppinss/exception": { 1146 + "version": "1.2.3", 1147 + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", 1148 + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", 1149 + "dev": true, 1150 + "license": "MIT" 1151 + }, 1152 + "node_modules/@sindresorhus/is": { 1153 + "version": "7.2.0", 1154 + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", 1155 + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", 1156 + "dev": true, 1157 + "license": "MIT", 1158 + "engines": { 1159 + "node": ">=18" 1160 + }, 1161 + "funding": { 1162 + "url": "https://github.com/sindresorhus/is?sponsor=1" 1163 + } 1164 + }, 1165 + "node_modules/@speed-highlight/core": { 1166 + "version": "1.2.15", 1167 + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.15.tgz", 1168 + "integrity": "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==", 1169 + "dev": true, 1170 + "license": "CC0-1.0" 1171 + }, 1172 + "node_modules/blake3-wasm": { 1173 + "version": "2.1.5", 1174 + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", 1175 + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", 1176 + "dev": true, 1177 + "license": "MIT" 1178 + }, 1179 + "node_modules/cookie": { 1180 + "version": "1.1.1", 1181 + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", 1182 + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", 1183 + "dev": true, 1184 + "license": "MIT", 1185 + "engines": { 1186 + "node": ">=18" 1187 + }, 1188 + "funding": { 1189 + "type": "opencollective", 1190 + "url": "https://opencollective.com/express" 1191 + } 1192 + }, 1193 + "node_modules/countries-list": { 1194 + "version": "3.3.0", 1195 + "resolved": "https://registry.npmjs.org/countries-list/-/countries-list-3.3.0.tgz", 1196 + "integrity": "sha512-XRUjS+dcZuNh/fg3+mka3bXgcg4TbQZ1gaK5IJqO6qulerBANl1bmrd20P2dgmPkBpP+5FnejiSF1gd7bgAg+g==", 1197 + "license": "MIT" 1198 + }, 1199 + "node_modules/detect-libc": { 1200 + "version": "2.1.2", 1201 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 1202 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 1203 + "dev": true, 1204 + "license": "Apache-2.0", 1205 + "engines": { 1206 + "node": ">=8" 1207 + } 1208 + }, 1209 + "node_modules/error-stack-parser-es": { 1210 + "version": "1.0.5", 1211 + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", 1212 + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", 1213 + "dev": true, 1214 + "license": "MIT", 1215 + "funding": { 1216 + "url": "https://github.com/sponsors/antfu" 1217 + } 1218 + }, 1219 + "node_modules/esbuild": { 1220 + "version": "0.27.3", 1221 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", 1222 + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", 1223 + "dev": true, 1224 + "hasInstallScript": true, 1225 + "license": "MIT", 1226 + "bin": { 1227 + "esbuild": "bin/esbuild" 1228 + }, 1229 + "engines": { 1230 + "node": ">=18" 1231 + }, 1232 + "optionalDependencies": { 1233 + "@esbuild/aix-ppc64": "0.27.3", 1234 + "@esbuild/android-arm": "0.27.3", 1235 + "@esbuild/android-arm64": "0.27.3", 1236 + "@esbuild/android-x64": "0.27.3", 1237 + "@esbuild/darwin-arm64": "0.27.3", 1238 + "@esbuild/darwin-x64": "0.27.3", 1239 + "@esbuild/freebsd-arm64": "0.27.3", 1240 + "@esbuild/freebsd-x64": "0.27.3", 1241 + "@esbuild/linux-arm": "0.27.3", 1242 + "@esbuild/linux-arm64": "0.27.3", 1243 + "@esbuild/linux-ia32": "0.27.3", 1244 + "@esbuild/linux-loong64": "0.27.3", 1245 + "@esbuild/linux-mips64el": "0.27.3", 1246 + "@esbuild/linux-ppc64": "0.27.3", 1247 + "@esbuild/linux-riscv64": "0.27.3", 1248 + "@esbuild/linux-s390x": "0.27.3", 1249 + "@esbuild/linux-x64": "0.27.3", 1250 + "@esbuild/netbsd-arm64": "0.27.3", 1251 + "@esbuild/netbsd-x64": "0.27.3", 1252 + "@esbuild/openbsd-arm64": "0.27.3", 1253 + "@esbuild/openbsd-x64": "0.27.3", 1254 + "@esbuild/openharmony-arm64": "0.27.3", 1255 + "@esbuild/sunos-x64": "0.27.3", 1256 + "@esbuild/win32-arm64": "0.27.3", 1257 + "@esbuild/win32-ia32": "0.27.3", 1258 + "@esbuild/win32-x64": "0.27.3" 1259 + } 1260 + }, 1261 + "node_modules/fsevents": { 1262 + "version": "2.3.3", 1263 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1264 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1265 + "dev": true, 1266 + "hasInstallScript": true, 1267 + "license": "MIT", 1268 + "optional": true, 1269 + "os": [ 1270 + "darwin" 1271 + ], 1272 + "engines": { 1273 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1274 + } 1275 + }, 1276 + "node_modules/kleur": { 1277 + "version": "4.1.5", 1278 + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", 1279 + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", 1280 + "dev": true, 1281 + "license": "MIT", 1282 + "engines": { 1283 + "node": ">=6" 1284 + } 1285 + }, 1286 + "node_modules/marked": { 1287 + "version": "15.0.12", 1288 + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", 1289 + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", 1290 + "license": "MIT", 1291 + "bin": { 1292 + "marked": "bin/marked.js" 1293 + }, 1294 + "engines": { 1295 + "node": ">= 18" 1296 + } 1297 + }, 1298 + "node_modules/miniflare": { 1299 + "version": "4.20260426.0", 1300 + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260426.0.tgz", 1301 + "integrity": "sha512-KM+v76d04qT+NsPfVKVQEgnnuLNE3uzCCl2QKMTJ5OXor5JbBm1vpkQwQ+l7o5ELCrZ74RnyKhJKLiJyUA39Tw==", 1302 + "dev": true, 1303 + "license": "MIT", 1304 + "dependencies": { 1305 + "@cspotcode/source-map-support": "0.8.1", 1306 + "sharp": "^0.34.5", 1307 + "undici": "7.24.8", 1308 + "workerd": "1.20260426.1", 1309 + "ws": "8.18.0", 1310 + "youch": "4.1.0-beta.10" 1311 + }, 1312 + "bin": { 1313 + "miniflare": "bootstrap.js" 1314 + }, 1315 + "engines": { 1316 + "node": ">=18.0.0" 1317 + } 1318 + }, 1319 + "node_modules/path-to-regexp": { 1320 + "version": "6.3.0", 1321 + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", 1322 + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", 1323 + "dev": true, 1324 + "license": "MIT" 1325 + }, 1326 + "node_modules/pathe": { 1327 + "version": "2.0.3", 1328 + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", 1329 + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", 1330 + "dev": true, 1331 + "license": "MIT" 1332 + }, 1333 + "node_modules/semver": { 1334 + "version": "7.7.4", 1335 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", 1336 + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", 1337 + "dev": true, 1338 + "license": "ISC", 1339 + "bin": { 1340 + "semver": "bin/semver.js" 1341 + }, 1342 + "engines": { 1343 + "node": ">=10" 1344 + } 1345 + }, 1346 + "node_modules/sharp": { 1347 + "version": "0.34.5", 1348 + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", 1349 + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", 1350 + "dev": true, 1351 + "hasInstallScript": true, 1352 + "license": "Apache-2.0", 1353 + "dependencies": { 1354 + "@img/colour": "^1.0.0", 1355 + "detect-libc": "^2.1.2", 1356 + "semver": "^7.7.3" 1357 + }, 1358 + "engines": { 1359 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1360 + }, 1361 + "funding": { 1362 + "url": "https://opencollective.com/libvips" 1363 + }, 1364 + "optionalDependencies": { 1365 + "@img/sharp-darwin-arm64": "0.34.5", 1366 + "@img/sharp-darwin-x64": "0.34.5", 1367 + "@img/sharp-libvips-darwin-arm64": "1.2.4", 1368 + "@img/sharp-libvips-darwin-x64": "1.2.4", 1369 + "@img/sharp-libvips-linux-arm": "1.2.4", 1370 + "@img/sharp-libvips-linux-arm64": "1.2.4", 1371 + "@img/sharp-libvips-linux-ppc64": "1.2.4", 1372 + "@img/sharp-libvips-linux-riscv64": "1.2.4", 1373 + "@img/sharp-libvips-linux-s390x": "1.2.4", 1374 + "@img/sharp-libvips-linux-x64": "1.2.4", 1375 + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", 1376 + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", 1377 + "@img/sharp-linux-arm": "0.34.5", 1378 + "@img/sharp-linux-arm64": "0.34.5", 1379 + "@img/sharp-linux-ppc64": "0.34.5", 1380 + "@img/sharp-linux-riscv64": "0.34.5", 1381 + "@img/sharp-linux-s390x": "0.34.5", 1382 + "@img/sharp-linux-x64": "0.34.5", 1383 + "@img/sharp-linuxmusl-arm64": "0.34.5", 1384 + "@img/sharp-linuxmusl-x64": "0.34.5", 1385 + "@img/sharp-wasm32": "0.34.5", 1386 + "@img/sharp-win32-arm64": "0.34.5", 1387 + "@img/sharp-win32-ia32": "0.34.5", 1388 + "@img/sharp-win32-x64": "0.34.5" 1389 + } 1390 + }, 1391 + "node_modules/supports-color": { 1392 + "version": "10.2.2", 1393 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", 1394 + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", 1395 + "dev": true, 1396 + "license": "MIT", 1397 + "engines": { 1398 + "node": ">=18" 1399 + }, 1400 + "funding": { 1401 + "url": "https://github.com/chalk/supports-color?sponsor=1" 1402 + } 1403 + }, 1404 + "node_modules/tslib": { 1405 + "version": "2.8.1", 1406 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 1407 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 1408 + "dev": true, 1409 + "license": "0BSD", 1410 + "optional": true 1411 + }, 1412 + "node_modules/typescript": { 1413 + "version": "5.9.3", 1414 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 1415 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 1416 + "dev": true, 1417 + "license": "Apache-2.0", 1418 + "bin": { 1419 + "tsc": "bin/tsc", 1420 + "tsserver": "bin/tsserver" 1421 + }, 1422 + "engines": { 1423 + "node": ">=14.17" 1424 + } 1425 + }, 1426 + "node_modules/undici": { 1427 + "version": "7.24.8", 1428 + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.8.tgz", 1429 + "integrity": "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==", 1430 + "dev": true, 1431 + "license": "MIT", 1432 + "engines": { 1433 + "node": ">=20.18.1" 1434 + } 1435 + }, 1436 + "node_modules/unenv": { 1437 + "version": "2.0.0-rc.24", 1438 + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", 1439 + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", 1440 + "dev": true, 1441 + "license": "MIT", 1442 + "peer": true, 1443 + "dependencies": { 1444 + "pathe": "^2.0.3" 1445 + } 1446 + }, 1447 + "node_modules/workerd": { 1448 + "version": "1.20260426.1", 1449 + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260426.1.tgz", 1450 + "integrity": "sha512-ELvGgN8c9oo+E6EPyecxk1TEf6/eAK4TxxQTW5mQ87C7jbjCzhMbg0P2ije49UBHV0dkBYPJcJvcklUltipl2A==", 1451 + "dev": true, 1452 + "hasInstallScript": true, 1453 + "license": "Apache-2.0", 1454 + "peer": true, 1455 + "bin": { 1456 + "workerd": "bin/workerd" 1457 + }, 1458 + "engines": { 1459 + "node": ">=16" 1460 + }, 1461 + "optionalDependencies": { 1462 + "@cloudflare/workerd-darwin-64": "1.20260426.1", 1463 + "@cloudflare/workerd-darwin-arm64": "1.20260426.1", 1464 + "@cloudflare/workerd-linux-64": "1.20260426.1", 1465 + "@cloudflare/workerd-linux-arm64": "1.20260426.1", 1466 + "@cloudflare/workerd-windows-64": "1.20260426.1" 1467 + } 1468 + }, 1469 + "node_modules/wrangler": { 1470 + "version": "4.86.0", 1471 + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.86.0.tgz", 1472 + "integrity": "sha512-9aa/gbF/HiUeeUEwyQpW5LDPBEzyt7iaE6xHwm0vk2Ly8A6J+jh03pzchqVnCCWR832mNyA28MD8oAYt0Kfvlw==", 1473 + "dev": true, 1474 + "license": "MIT OR Apache-2.0", 1475 + "dependencies": { 1476 + "@cloudflare/kv-asset-handler": "0.4.2", 1477 + "@cloudflare/unenv-preset": "2.16.1", 1478 + "blake3-wasm": "2.1.5", 1479 + "esbuild": "0.27.3", 1480 + "miniflare": "4.20260426.0", 1481 + "path-to-regexp": "6.3.0", 1482 + "unenv": "2.0.0-rc.24", 1483 + "workerd": "1.20260426.1" 1484 + }, 1485 + "bin": { 1486 + "wrangler": "bin/wrangler.js", 1487 + "wrangler2": "bin/wrangler.js" 1488 + }, 1489 + "engines": { 1490 + "node": ">=20.3.0" 1491 + }, 1492 + "optionalDependencies": { 1493 + "fsevents": "~2.3.2" 1494 + }, 1495 + "peerDependencies": { 1496 + "@cloudflare/workers-types": "^4.20260426.1" 1497 + }, 1498 + "peerDependenciesMeta": { 1499 + "@cloudflare/workers-types": { 1500 + "optional": true 1501 + } 1502 + } 1503 + }, 1504 + "node_modules/ws": { 1505 + "version": "8.18.0", 1506 + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", 1507 + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", 1508 + "dev": true, 1509 + "license": "MIT", 1510 + "engines": { 1511 + "node": ">=10.0.0" 1512 + }, 1513 + "peerDependencies": { 1514 + "bufferutil": "^4.0.1", 1515 + "utf-8-validate": ">=5.0.2" 1516 + }, 1517 + "peerDependenciesMeta": { 1518 + "bufferutil": { 1519 + "optional": true 1520 + }, 1521 + "utf-8-validate": { 1522 + "optional": true 1523 + } 1524 + } 1525 + }, 1526 + "node_modules/youch": { 1527 + "version": "4.1.0-beta.10", 1528 + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", 1529 + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", 1530 + "dev": true, 1531 + "license": "MIT", 1532 + "dependencies": { 1533 + "@poppinss/colors": "^4.1.5", 1534 + "@poppinss/dumper": "^0.6.4", 1535 + "@speed-highlight/core": "^1.2.7", 1536 + "cookie": "^1.0.2", 1537 + "youch-core": "^0.3.3" 1538 + } 1539 + }, 1540 + "node_modules/youch-core": { 1541 + "version": "0.3.3", 1542 + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", 1543 + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", 1544 + "dev": true, 1545 + "license": "MIT", 1546 + "dependencies": { 1547 + "@poppinss/exception": "^1.2.2", 1548 + "error-stack-parser-es": "^1.0.5" 1549 + } 1550 + } 1551 + } 1552 + }
+18
jobs/package.json
··· 1 + { 2 + "name": "tangled-jobs", 3 + "version": "1.0.0", 4 + "private": true, 5 + "scripts": { 6 + "dev": "wrangler dev", 7 + "deploy": "wrangler deploy" 8 + }, 9 + "dependencies": { 10 + "countries-list": "^3.1.1", 11 + "marked": "^15.0.0" 12 + }, 13 + "devDependencies": { 14 + "@cloudflare/workers-types": "^4.20250428.0", 15 + "typescript": "^5.8.3", 16 + "wrangler": "^4.13.2" 17 + } 18 + }
+599
jobs/src/index.ts
··· 1 + import { marked } from 'marked'; 2 + import { getEmojiFlag, countries } from 'countries-list'; 3 + 4 + export interface Env { 5 + LINEAR_API_KEY: string; 6 + LINEAR_TEAM_ID: string; 7 + } 8 + 9 + // --------------------------------------------------------------------------- 10 + // Job postings — add a new markdown file in src/postings/ and import it here 11 + // --------------------------------------------------------------------------- 12 + import softwareEngineer from './postings/software-engineer.md'; 13 + 14 + interface Posting { 15 + slug: string; 16 + title: string; 17 + location: string; 18 + type: string; 19 + salary: string; 20 + body: string; // raw markdown (frontmatter stripped) 21 + } 22 + 23 + function parsePosting(slug: string, raw: string): Posting { 24 + const fm = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); 25 + if (!fm) throw new Error(`Posting ${slug} is missing frontmatter`); 26 + 27 + const meta: Record<string, string> = {}; 28 + for (const line of fm[1].split('\n')) { 29 + const [k, ...rest] = line.split(':'); 30 + if (k && rest.length) meta[k.trim()] = rest.join(':').trim(); 31 + } 32 + 33 + return { 34 + slug, 35 + title: meta['title'] ?? slug, 36 + location: meta['location'] ?? 'Remote', 37 + type: meta['type'] ?? 'Full-time', 38 + salary: meta['salary'] ?? '', 39 + body: fm[2].trim(), 40 + }; 41 + } 42 + 43 + const POSTINGS: Posting[] = [ 44 + parsePosting('software-engineer', softwareEngineer as string), 45 + // add more here as you create markdown files 46 + ]; 47 + 48 + const POSTINGS_BY_SLUG = new Map(POSTINGS.map((p) => [p.slug, p])); 49 + 50 + // --------------------------------------------------------------------------- 51 + // Country list (sorted alphabetically by name, from countries-list) 52 + // --------------------------------------------------------------------------- 53 + const COUNTRIES: { code: string; name: string }[] = Object.entries(countries) 54 + .map(([code, c]) => ({ code, name: c.name })) 55 + .sort((a, b) => a.name.localeCompare(b.name)); 56 + 57 + // --------------------------------------------------------------------------- 58 + // SVG logo 59 + // --------------------------------------------------------------------------- 60 + const DOLLY_SVG = `<svg class="size-7" width="25" height="25" viewBox="0 0 25 25" xmlns="http://www.w3.org/2000/svg"> 61 + <style>.dolly{color:#000}@media(prefers-color-scheme:dark){.dolly{color:#fff}}</style> 62 + <g transform="translate(-0.42924038,-0.87777209)"> 63 + <path class="dolly" fill="currentColor" style="stroke-width:0.111183" d="m 16.775491,24.987061 c -0.78517,-0.0064 -1.384202,-0.234614 -2.033994,-0.631295 -0.931792,-0.490188 -1.643475,-1.31368 -2.152014,-2.221647 C 11.781409,23.136647 10.701392,23.744942 9.4922931,24.0886 8.9774725,24.238111 8.0757679,24.389777 6.5811304,23.84827 4.4270703,23.124679 2.8580086,20.883331 3.0363279,18.599583 3.0037061,17.652919 3.3488675,16.723769 3.8381157,15.925061 2.5329485,15.224503 1.4686756,14.048584 1.0611184,12.606459 0.81344502,11.816973 0.82385989,10.966486 0.91519098,10.154906 1.2422711,8.2387903 2.6795811,6.5725716 4.5299585,5.9732484 5.2685364,4.290122 6.8802592,3.0349975 8.706276,2.7794663 c 1.2124148,-0.1688264 2.46744,0.084987 3.52811,0.7011837 1.545426,-1.7139736 4.237779,-2.2205077 6.293579,-1.1676231 1.568222,0.7488935 2.689625,2.3113526 2.961888,4.0151464 1.492195,0.5977882 2.749007,1.8168898 3.242225,3.3644951 0.329805,0.9581836 0.340709,2.0135956 0.127128,2.9974286 -0.381606,1.535184 -1.465322,2.842146 -2.868035,3.556463 0.0034,0.273204 0.901506,2.243045 0.751284,3.729647 -0.03281,1.858525 -1.211631,3.619894 -2.846433,4.475452 -0.953967,0.556812 -2.084452,0.546309 -3.120531,0.535398 z m -4.470079,-5.349839 c 1.322246,-0.147248 2.189053,-1.300106 2.862307,-2.338363 0.318287,-0.472954 0.561404,-1.002348 0.803,-1.505815 0.313265,0.287151 0.578698,0.828085 1.074141,0.956909 0.521892,0.162542 1.133743,0.03052 1.45325,-0.443554 0.611414,-1.140449 0.31004,-2.516537 -0.04602,-3.698347 C 18.232844,11.92927 17.945151,11.232927 17.397785,10.751793 17.514522,9.9283111 17.026575,9.0919791 16.332883,8.6609491 15.741721,9.1323278 14.842258,9.1294949 14.271975,8.6252369 13.178927,9.7400102 12.177239,9.7029996 11.209704,8.8195135 10.992255,8.6209543 10.577326,10.031484 9.1211947,9.2324497 8.2846288,9.9333947 7.6359672,10.607693 7.0611981,11.578553 6.5026891,12.62523 5.9177873,13.554793 5.867393,14.69141 c -0.024234,0.66432 0.4948601,1.360337 1.1982269,1.306329 0.702996,0.06277 1.1815208,-0.629091 1.7138087,-0.916491 0.079382,0.927141 0.1688108,1.923227 0.4821259,2.828358 0.3596254,1.171275 1.6262605,1.915695 2.8251855,1.745211 0.08481,-0.0066 0.218672,-0.01769 0.218672,-0.0176 z m 0.686342,-3.497495 c -0.643126,-0.394168 -0.33365,-1.249599 -0.359402,-1.870938 0.064,-0.749774 0.115321,-1.538054 0.452402,-2.221125 0.356724,-0.487008 1.226721,-0.299139 1.265134,0.325689 -0.02558,0.628509 -0.314101,1.25416 -0.279646,1.9057 -0.07482,0.544043 0.05418,1.155133 -0.186476,1.652391 -0.197455,0.275121 -0.599638,0.355105 -0.892012,0.208283 z m -2.808766,-0.358124 c -0.605767,-0.328664 -0.4133176,-1.155655 -0.5083256,-1.73063 0.078762,-0.66567 0.013203,-1.510085 0.5705316,-1.976886 0.545037,-0.380109 1.286917,0.270803 1.029164,0.868384 -0.274913,0.755214 -0.09475,1.580345 -0.08893,2.34609 -0.104009,0.451702 -0.587146,0.691508 -1.002445,0.493042 z"/> 64 + </g> 65 + </svg>`; 66 + 67 + // --------------------------------------------------------------------------- 68 + // HTML shell 69 + // --------------------------------------------------------------------------- 70 + function page(title: string, body: string): string { 71 + return `<!DOCTYPE html> 72 + <html lang="en"> 73 + <head> 74 + <meta charset="UTF-8"> 75 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 76 + <title>${escapeHtml(title)} &middot; jobs at tangled</title> 77 + <link rel="icon" type="image/svg+xml" href="/favicon.svg"> 78 + <meta property="og:image" content="https://assets.tangled.network/jobs-og.png"> 79 + <meta name="twitter:card" content="summary_large_image"> 80 + <meta name="twitter:image" content="https://assets.tangled.network/jobs-og.png"> 81 + <link rel="preconnect" href="https://rsms.me/"> 82 + <link rel="stylesheet" href="https://rsms.me/inter/inter.css"> 83 + <script src="https://cdn.tailwindcss.com?plugins=typography"></script> 84 + <script> 85 + tailwind.config = { 86 + darkMode: 'media', 87 + theme: { 88 + extend: { 89 + fontFamily: { 90 + sans: ['"InterVariable"', '"Inter"', 'system-ui', 'sans-serif'], 91 + mono: ['"IBM Plex Mono"', 'ui-monospace', 'monospace'], 92 + } 93 + } 94 + } 95 + } 96 + </script> 97 + <style> 98 + html { font-size: 14px; } 99 + ::selection { background-color: rgba(250,204,21,0.3); } 100 + @media (prefers-color-scheme: dark) { 101 + ::selection { background-color: rgba(202,138,4,0.5); color:#fff; } 102 + } 103 + a { color: inherit; text-decoration: none; } 104 + a:hover { text-decoration: underline; } 105 + label { display:block; font-size:0.875rem; padding:0.5rem 0; color:#111827; } 106 + @media (prefers-color-scheme: dark) { label { color:#f3f4f6; } } 107 + .btn-create { 108 + position:relative; z-index:10; display:inline-flex; min-height:30px; 109 + cursor:pointer; align-items:center; justify-content:center; 110 + background:transparent; padding:0.25rem 0.75rem; font-size:0.875rem; 111 + color:#fff; border:none; font-family:inherit; text-decoration:none; 112 + } 113 + .btn-create::before { 114 + content:''; position:absolute; inset:0; z-index:-10; display:block; 115 + border-radius:0.25rem; border:1px solid #15803d; background:#16a34a; 116 + box-shadow:inset 0 -2px 0 0 rgba(0,0,0,.1),0 1px 0 0 rgba(0,0,0,.04); 117 + transition:all .15s ease-in-out; 118 + } 119 + .btn-create:hover { text-decoration: none; } 120 + .btn-create:hover::before { background:#15803d; border-color:#166534; } 121 + .btn-create:active::before { box-shadow:inset 0 2px 2px 0 rgba(0,0,0,.1); } 122 + .btn-create:disabled { cursor:not-allowed; opacity:.5; } 123 + @media (prefers-color-scheme: dark) { 124 + .btn-create::before { background:#15803d; border-color:#166534; } 125 + .btn-create:hover::before { background:#166534; } 126 + } 127 + </style> 128 + </head> 129 + <body class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white min-h-screen flex flex-col"> 130 + 131 + <header class="max-w-screen-xl mx-auto w-full"> 132 + <nav class="mx-auto space-x-4 px-6 py-2"> 133 + <div class="flex justify-between p-0 items-center"> 134 + <div> 135 + <a href="/" class="text-2xl no-underline hover:no-underline flex items-center gap-2"> 136 + ${DOLLY_SVG.replace('class="size-7"', 'class="size-8 text-black dark:text-white"')} 137 + <span class="font-bold text-xl not-italic">tangled</span> 138 + </a> 139 + </div> 140 + </div> 141 + </div> 142 + </nav> 143 + </header> 144 + 145 + ${body} 146 + 147 + <footer class="mt-12 w-full px-6 py-4"> 148 + <div class="max-w-[90ch] mx-auto flex flex-wrap justify-center items-center gap-x-4 gap-y-2 text-sm text-gray-500 dark:text-gray-400"> 149 + <div class="flex items-center justify-center gap-x-2 order-last sm:order-first w-full sm:w-auto"> 150 + <a href="https://tangled.org" class="no-underline hover:no-underline flex items-center">${DOLLY_SVG.replace('class="size-7"', 'class="size-5 text-gray-500 dark:text-gray-400"')}</a> 151 + <span>&copy; 2026 Tangled Labs Oy.</span> 152 + </div> 153 + <a href="https://docs.tangled.org" class="hover:text-gray-900 dark:hover:text-gray-200 hover:underline no-underline">docs</a> 154 + <a href="https://tangled.org/@tangled.org/core" class="hover:text-gray-900 dark:hover:text-gray-200 hover:underline no-underline">source</a> 155 + <a href="https://chat.tangled.org" target="_blank" rel="noopener noreferrer" class="hover:text-gray-900 dark:hover:text-gray-200 hover:underline no-underline">discord</a> 156 + <a href="https://bsky.app/profile/tangled.org" target="_blank" rel="noopener noreferrer" class="hover:text-gray-900 dark:hover:text-gray-200 hover:underline no-underline">bluesky</a> 157 + <a href="https://x.com/tangled_org" target="_blank" rel="noopener noreferrer" class="hover:text-gray-900 dark:hover:text-gray-200 hover:underline no-underline">twitter (x)</a> 158 + </div> 159 + </footer> 160 + 161 + </body> 162 + </html>`; 163 + } 164 + 165 + // --------------------------------------------------------------------------- 166 + // Pages 167 + // --------------------------------------------------------------------------- 168 + function listingsPage(): string { 169 + const rows = POSTINGS.map( 170 + (p) => ` 171 + <a href="/${p.slug}" class="no-underline hover:no-underline group flex items-center justify-between gap-4 px-6 py-4 hover:bg-gray-50 hover:dark:bg-gray-800/50 transition-colors"> 172 + <div> 173 + <div class="font-medium text-gray-900 dark:text-white group-hover:underline">${escapeHtml(p.title)}</div> 174 + <div class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">${escapeHtml(p.location)} · ${escapeHtml(p.type)}${p.salary ? ` · ${escapeHtml(p.salary)}` : ''}</div> 175 + </div> 176 + <span class="text-gray-400 dark:text-gray-500 shrink-0">→</span> 177 + </a>`, 178 + ).join(''); 179 + 180 + const body = ` 181 + <main class="max-w-[90ch] mx-auto w-full px-4 py-10 flex-1"> 182 + <header class="mb-10"> 183 + <h1 class="text-3xl font-bold dark:text-white mb-2">Open positions</h1> 184 + <p class="text-gray-500 dark:text-gray-400 max-w-prose"> 185 + We're a lean, globally distributed team working on building the next-generation of social coding. 186 + We work remotely and try to meet once a year in-person. 187 + </p> 188 + </header> 189 + 190 + ${ 191 + POSTINGS.length === 0 192 + ? `<p class="p-10 italic text-gray-500 dark:text-gray-400">No open positions right now; check back soon.</p>` 193 + : `<div class="rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700">${rows}</div>` 194 + } 195 + </main>`; 196 + 197 + return page('open positions', body); 198 + } 199 + 200 + function jobPage(posting: Posting): string { 201 + const bodyHtml = marked.parse(posting.body) as string; 202 + 203 + const body = ` 204 + <main class="max-w-[90ch] mx-auto w-full px-4 py-10 flex-1"> 205 + <div class="mb-2"> 206 + <a href="/" class="text-sm text-gray-500 dark:text-gray-400 hover:underline no-underline">← All positions</a> 207 + </div> 208 + 209 + <header class="mb-8 not-prose"> 210 + <p class="text-sm text-gray-500 dark:text-gray-400 mb-1">${escapeHtml(posting.location)} · ${escapeHtml(posting.type)}${posting.salary ? ` · ${escapeHtml(posting.salary)}` : ''}</p> 211 + <h1 class="text-2xl font-bold dark:text-white">${escapeHtml(posting.title)}</h1> 212 + </header> 213 + 214 + <div class="prose dark:prose-invert prose-headings:no-underline text-[15px] w-full max-w-none mb-10"> 215 + ${bodyHtml} 216 + </div> 217 + 218 + <div class="border-t border-gray-200 dark:border-gray-700 pt-8"> 219 + <h2 class="font-bold text-lg dark:text-white mb-1">Apply for this role</h2> 220 + <p class="text-base text-gray-500 dark:text-gray-400 mb-6">We read every application; if there's a fit, we'll be in touch via email.</p> 221 + ${applyForm(posting.slug)} 222 + </div> 223 + </main>`; 224 + 225 + return page(posting.title, body); 226 + } 227 + 228 + function applyForm(slug: string, error?: string): string { 229 + const errorHtml = error 230 + ? `<div class="mb-6 rounded border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20 px-4 py-3 text-sm text-red-600 dark:text-red-400">${escapeHtml(error)}</div>` 231 + : ''; 232 + 233 + const inputClass = 'block w-full rounded p-3 bg-gray-50 dark:bg-gray-800 dark:text-white border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-1 focus:ring-gray-400 dark:focus:ring-gray-500'; 234 + const selectClass = `${inputClass} appearance-none`; 235 + 236 + return ` 237 + ${errorHtml} 238 + <form method="POST" action="/${slug}/apply" id="apply-form" class="space-y-4" enctype="multipart/form-data"> 239 + <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> 240 + <div> 241 + <label for="first_name">First name <span class="text-red-400">*</span></label> 242 + <input type="text" id="first_name" name="first_name" required placeholder="Jane" autocomplete="given-name" class="${inputClass}"> 243 + </div> 244 + <div> 245 + <label for="last_name">Last name <span class="text-red-400">*</span></label> 246 + <input type="text" id="last_name" name="last_name" required placeholder="Smith" autocomplete="family-name" class="${inputClass}"> 247 + </div> 248 + </div> 249 + 250 + <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> 251 + <div> 252 + <label for="country">Country <span class="text-red-400">*</span></label> 253 + <select id="country" name="country" required class="${selectClass}"> 254 + <option value="">Select a country…</option> 255 + ${COUNTRIES.map((c) => `<option value="${escapeHtml(c.name)}">${escapeHtml(c.name)} ${getEmojiFlag(c.code as Parameters<typeof getEmojiFlag>[0])}</option>`).join('')} 256 + </select> 257 + </div> 258 + <div> 259 + <label for="city">City <span class="text-red-400">*</span></label> 260 + <input type="text" id="city" name="city" required placeholder="Your city" autocomplete="address-level2" class="${inputClass}"> 261 + </div> 262 + </div> 263 + 264 + <div> 265 + <label for="email">Email <span class="text-red-400">*</span></label> 266 + <input type="email" id="email" name="email" required placeholder="you@example.com" autocomplete="email" class="${inputClass}"> 267 + </div> 268 + 269 + <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> 270 + <div> 271 + <label for="portfolio">Portfolio <span class="text-gray-400 font-normal">(optional)</span></label> 272 + <input type="url" id="portfolio" name="portfolio" placeholder="https://…" class="${inputClass}"> 273 + </div> 274 + <div> 275 + <label for="linkedin">LinkedIn <span class="text-gray-400 font-normal">(optional)</span></label> 276 + <input type="url" id="linkedin" name="linkedin" placeholder="https://linkedin.com/in/…" class="${inputClass}"> 277 + </div> 278 + </div> 279 + 280 + <div> 281 + <label for="resume">Résumé / CV <span class="text-red-400">*</span> <span class="text-gray-400 font-normal">(PDF)</span></label> 282 + <input type="file" id="resume" name="resume" accept=".pdf,application/pdf" required class="${inputClass}"> 283 + </div> 284 + 285 + <div> 286 + <label for="cover">Cover letter <span class="text-red-400">*</span></label> 287 + <textarea id="cover" name="cover" required rows="10" placeholder="Tell us about yourself and why you want to work at Tangled." class="${inputClass}"></textarea> 288 + </div> 289 + 290 + <div class="flex items-center gap-4 pt-2"> 291 + <button type="submit" class="btn-create" id="submit-btn">Submit application →</button> 292 + </div> 293 + <p class="text-sm text-gray-400 dark:text-gray-500 mt-6">By submitting this form you agree to your data being processed by our subprocessors: Cloudflare and Linear.</p> 294 + </form> 295 + <script> 296 + document.getElementById('apply-form').addEventListener('submit', function() { 297 + var btn = document.getElementById('submit-btn'); 298 + btn.disabled = true; 299 + btn.textContent = 'Submitting…'; 300 + }); 301 + </script>`; 302 + } 303 + 304 + function successPage(firstName: string, posting: Posting): string { 305 + const body = ` 306 + <main class="max-w-[90ch] mx-auto w-full px-4 py-16 text-center"> 307 + <div class="mb-6 text-5xl">🎉</div> 308 + <h1 class="text-2xl font-bold dark:text-white mb-3">Application received</h1> 309 + <p class="text-gray-500 dark:text-gray-400 max-w-md mx-auto mb-8"> 310 + Thanks, ${escapeHtml(firstName)}! We've received your application for <strong class="text-gray-700 dark:text-gray-300">${escapeHtml(posting.title)}</strong> and will be in touch soon. 311 + </p> 312 + <a href="/" class="btn-create">← View all positions</a> 313 + </main>`; 314 + 315 + return page('application received', body); 316 + } 317 + 318 + // --------------------------------------------------------------------------- 319 + // Linear 320 + // --------------------------------------------------------------------------- 321 + 322 + // Upload a file to Linear's asset storage and return the public asset URL. 323 + async function uploadToLinear(env: Env, file: File): Promise<string> { 324 + const query = ` 325 + mutation FileUpload($contentType: String!, $filename: String!, $size: Int!) { 326 + fileUpload(contentType: $contentType, filename: $filename, size: $size) { 327 + uploadFile { 328 + uploadUrl 329 + assetUrl 330 + headers { key value } 331 + } 332 + } 333 + }`; 334 + 335 + const metaResp = await fetch('https://api.linear.app/graphql', { 336 + method: 'POST', 337 + headers: { Authorization: env.LINEAR_API_KEY, 'Content-Type': 'application/json' }, 338 + body: JSON.stringify({ 339 + query, 340 + variables: { contentType: file.type, filename: file.name, size: file.size }, 341 + }), 342 + }); 343 + 344 + const meta = (await metaResp.json()) as { 345 + data?: { fileUpload?: { uploadFile?: { uploadUrl: string; assetUrl: string; headers: { key: string; value: string }[] } } }; 346 + errors?: unknown[]; 347 + }; 348 + 349 + const uploadFile = meta.data?.fileUpload?.uploadFile; 350 + if (!uploadFile || meta.errors) { 351 + throw new Error(`Linear fileUpload error: ${JSON.stringify(meta.errors ?? meta)}`); 352 + } 353 + 354 + const uploadHeaders: Record<string, string> = { 'Content-Type': file.type }; 355 + for (const { key, value } of uploadFile.headers) uploadHeaders[key] = value; 356 + 357 + const uploadResp = await fetch(uploadFile.uploadUrl, { 358 + method: 'PUT', 359 + headers: uploadHeaders, 360 + body: await file.arrayBuffer(), 361 + }); 362 + 363 + if (!uploadResp.ok) { 364 + throw new Error(`Resume upload failed: ${uploadResp.status} ${uploadResp.statusText}`); 365 + } 366 + 367 + return uploadFile.assetUrl; 368 + } 369 + 370 + async function getOrCreateLabel(env: Env, name: string): Promise<string | null> { 371 + try { 372 + const searchQuery = ` 373 + query Labels($teamId: ID!) { 374 + issueLabels(filter: { team: { id: { eq: $teamId } } }) { 375 + nodes { id name } 376 + } 377 + }`; 378 + 379 + const searchResp = await fetch('https://api.linear.app/graphql', { 380 + method: 'POST', 381 + headers: { Authorization: env.LINEAR_API_KEY, 'Content-Type': 'application/json' }, 382 + body: JSON.stringify({ query: searchQuery, variables: { teamId: env.LINEAR_TEAM_ID } }), 383 + }); 384 + 385 + const searchResult = (await searchResp.json()) as { 386 + data?: { issueLabels?: { nodes: { id: string; name: string }[] } }; 387 + }; 388 + 389 + const existing = searchResult.data?.issueLabels?.nodes.find((l) => l.name === name); 390 + if (existing) return existing.id; 391 + 392 + const createQuery = ` 393 + mutation LabelCreate($input: IssueLabelCreateInput!) { 394 + issueLabelCreate(input: $input) { 395 + success 396 + issueLabel { id } 397 + } 398 + }`; 399 + 400 + const createResp = await fetch('https://api.linear.app/graphql', { 401 + method: 'POST', 402 + headers: { Authorization: env.LINEAR_API_KEY, 'Content-Type': 'application/json' }, 403 + body: JSON.stringify({ query: createQuery, variables: { input: { name, teamId: env.LINEAR_TEAM_ID } } }), 404 + }); 405 + 406 + const created = (await createResp.json()) as { 407 + data?: { issueLabelCreate?: { success: boolean; issueLabel: { id: string } } }; 408 + }; 409 + 410 + return created.data?.issueLabelCreate?.issueLabel?.id ?? null; 411 + } catch (err) { 412 + console.error('Failed to get/create label:', err); 413 + return null; 414 + } 415 + } 416 + 417 + async function createLinearIssue( 418 + env: Env, 419 + posting: Posting, 420 + data: { 421 + firstName: string; 422 + lastName: string; 423 + email: string; 424 + country: string; 425 + city: string; 426 + portfolio: string; 427 + linkedin: string; 428 + cover: string; 429 + resume: File | null; 430 + }, 431 + ): Promise<void> { 432 + const fullName = `${data.firstName} ${data.lastName}`; 433 + const title = fullName; 434 + 435 + const [resumeUrl, labelId] = await Promise.all([ 436 + data.resume ? uploadToLinear(env, data.resume) : Promise.resolve(null), 437 + getOrCreateLabel(env, posting.title), 438 + ]); 439 + 440 + const description = [ 441 + `**Name:** ${fullName}`, 442 + `**Email:** ${data.email}`, 443 + `**Location:** ${data.city}, ${data.country}`, 444 + `**Role:** ${posting.title}`, 445 + data.portfolio ? `**Portfolio:** ${data.portfolio}` : null, 446 + data.linkedin ? `**LinkedIn:** ${data.linkedin}` : null, 447 + resumeUrl ? `**Résumé:** [${data.resume!.name}](${resumeUrl})` : null, 448 + '', 449 + '---', 450 + '', 451 + '## Cover letter', 452 + '', 453 + '```', 454 + data.cover, 455 + '```', 456 + ] 457 + .filter((l): l is string => l !== null) 458 + .join('\n'); 459 + 460 + const query = ` 461 + mutation CreateIssue($input: IssueCreateInput!) { 462 + issueCreate(input: $input) { 463 + success 464 + issue { id identifier } 465 + } 466 + }`; 467 + 468 + const input: Record<string, unknown> = { title, description, teamId: env.LINEAR_TEAM_ID }; 469 + if (labelId) input.labelIds = [labelId]; 470 + 471 + const resp = await fetch('https://api.linear.app/graphql', { 472 + method: 'POST', 473 + headers: { Authorization: env.LINEAR_API_KEY, 'Content-Type': 'application/json' }, 474 + body: JSON.stringify({ query, variables: { input } }), 475 + }); 476 + 477 + const result = (await resp.json()) as { data?: { issueCreate?: { success: boolean } }; errors?: unknown[] }; 478 + 479 + if (result.errors || !result.data?.issueCreate?.success) { 480 + throw new Error(`Linear error: ${JSON.stringify(result.errors ?? result)}`); 481 + } 482 + } 483 + 484 + // --------------------------------------------------------------------------- 485 + // Router 486 + // --------------------------------------------------------------------------- 487 + export default { 488 + async fetch(request: Request, env: Env): Promise<Response> { 489 + const url = new URL(request.url); 490 + const parts = url.pathname.replace(/^\//, '').split('/'); 491 + 492 + // GET /favicon.svg 493 + if (request.method === 'GET' && url.pathname === '/favicon.svg') { 494 + return new Response(DOLLY_SVG, { headers: { 'Content-Type': 'image/svg+xml' } }); 495 + } 496 + 497 + // GET / — listings 498 + if (request.method === 'GET' && url.pathname === '/') { 499 + return html(listingsPage()); 500 + } 501 + 502 + // GET /:slug — job posting 503 + if (request.method === 'GET' && parts.length === 1 && parts[0]) { 504 + const posting = POSTINGS_BY_SLUG.get(parts[0]); 505 + if (!posting) return notFound(); 506 + return html(jobPage(posting)); 507 + } 508 + 509 + // POST /:slug/apply — submit application 510 + if (request.method === 'POST' && parts.length === 2 && parts[1] === 'apply') { 511 + const posting = POSTINGS_BY_SLUG.get(parts[0]); 512 + if (!posting) return notFound(); 513 + 514 + let formData: FormData; 515 + try { 516 + formData = await request.formData(); 517 + } catch { 518 + return html(jobPage(posting), 400); 519 + } 520 + 521 + const firstName = (formData.get('first_name') as string | null)?.trim() ?? ''; 522 + const lastName = (formData.get('last_name') as string | null)?.trim() ?? ''; 523 + const email = (formData.get('email') as string | null)?.trim() ?? ''; 524 + const country = (formData.get('country') as string | null)?.trim() ?? ''; 525 + const city = (formData.get('city') as string | null)?.trim() ?? ''; 526 + const portfolio = (formData.get('portfolio') as string | null)?.trim() ?? ''; 527 + const linkedin = (formData.get('linkedin') as string | null)?.trim() ?? ''; 528 + const cover = (formData.get('cover') as string | null)?.trim() ?? ''; 529 + const resumeEntry = formData.get('resume'); 530 + const resume = 531 + resumeEntry instanceof File && resumeEntry.size > 0 && resumeEntry.type === 'application/pdf' 532 + ? resumeEntry 533 + : null; 534 + 535 + if (!firstName || !lastName || !email || !country || !city || !cover || !resume) { 536 + const bodyHtml = marked.parse(posting.body) as string; 537 + const body = ` 538 + <main class="max-w-[90ch] mx-auto w-full px-4 py-10 flex-1"> 539 + <div class="mb-2"> 540 + <a href="/" class="text-sm text-gray-500 dark:text-gray-400 hover:underline no-underline">← All positions</a> 541 + </div> 542 + <header class="mb-8 not-prose"> 543 + <p class="text-sm text-gray-500 dark:text-gray-400 mb-1">${escapeHtml(posting.location)} · ${escapeHtml(posting.type)}${posting.salary ? ` · ${escapeHtml(posting.salary)}` : ''}</p> 544 + <h1 class="text-2xl font-bold dark:text-white">${escapeHtml(posting.title)}</h1> 545 + </header> 546 + <div class="prose dark:prose-invert prose-headings:no-underline text-[15px] w-full max-w-none mb-10">${bodyHtml}</div> 547 + <div class="border-t border-gray-200 dark:border-gray-700 pt-8"> 548 + <h2 class="font-bold text-lg dark:text-white mb-1">Apply for this role</h2> 549 + <p class="text-base text-gray-500 dark:text-gray-400 mb-6">We read every application; if there's a fit, we'll be in touch via email.</p> 550 + ${applyForm(posting.slug, 'Please fill in all required fields.')} 551 + </div> 552 + </main>`; 553 + return html(page(posting.title, body), 400); 554 + } 555 + 556 + try { 557 + await createLinearIssue(env, posting, { firstName, lastName, email, country, city, portfolio, linkedin, cover, resume }); 558 + return html(successPage(firstName, posting)); 559 + } catch (err) { 560 + console.error('Linear issue creation failed:', err); 561 + const bodyHtml = marked.parse(posting.body) as string; 562 + const body = ` 563 + <main class="max-w-[90ch] mx-auto w-full px-4 py-10 flex-1"> 564 + <div class="mb-2"> 565 + <a href="/" class="text-sm text-gray-500 dark:text-gray-400 hover:underline no-underline">← All positions</a> 566 + </div> 567 + <header class="mb-8 not-prose"> 568 + <p class="text-sm text-gray-500 dark:text-gray-400 mb-1">${escapeHtml(posting.location)} · ${escapeHtml(posting.type)}${posting.salary ? ` · ${escapeHtml(posting.salary)}` : ''}</p> 569 + <h1 class="text-2xl font-bold dark:text-white">${escapeHtml(posting.title)}</h1> 570 + </header> 571 + <div class="prose dark:prose-invert prose-headings:no-underline text-[15px] w-full max-w-none mb-10">${bodyHtml}</div> 572 + <div class="border-t border-gray-200 dark:border-gray-700 pt-8"> 573 + <h2 class="font-bold text-lg dark:text-white mb-1">Apply for this role</h2> 574 + <p class="text-base text-gray-500 dark:text-gray-400 mb-6">We read every application; if there's a fit, we'll be in touch via email.</p> 575 + ${applyForm(posting.slug, 'Something went wrong submitting your application. Please try again.')} 576 + </div> 577 + </main>`; 578 + return html(page(posting.title, body), 500); 579 + } 580 + } 581 + 582 + return notFound(); 583 + }, 584 + } satisfies ExportedHandler<Env>; 585 + 586 + // --------------------------------------------------------------------------- 587 + // Helpers 588 + // --------------------------------------------------------------------------- 589 + function html(body: string, status = 200): Response { 590 + return new Response(body, { status, headers: { 'Content-Type': 'text/html; charset=utf-8' } }); 591 + } 592 + 593 + function notFound(): Response { 594 + return new Response('not found', { status: 404 }); 595 + } 596 + 597 + function escapeHtml(str: string): string { 598 + return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;'); 599 + }
+16
jobs/src/postings/software-engineer.md
··· 1 + --- 2 + title: Software Engineer, Platform 3 + location: Remote 4 + type: Full-time 5 + salary: €100k–€120k 6 + --- 7 + 8 + We're looking for a Software Engineer to join us in building the 9 + next-generation code forge. 10 + 11 + **What you'll work on**: Primarily, our new CI platform: Go, Firecracker/QEMU, 12 + Nix and general distributed systems expertise. Ideally you check all these 13 + boxes. 14 + 15 + **What we offer**: Competitive (for Europe) salary, equity, benefits. We hire 16 + from anywhere in the world and don't location-adjust pay.
+14
jobs/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "lib": ["ES2022"], 5 + "module": "ES2022", 6 + "moduleResolution": "bundler", 7 + "types": ["@cloudflare/workers-types"], 8 + "strict": true, 9 + "noUnusedLocals": true, 10 + "noUnusedParameters": true, 11 + "noImplicitReturns": true 12 + }, 13 + "include": ["src/**/*.ts"] 14 + }
+23
jobs/wrangler.toml
··· 1 + name = "tangled-jobs" 2 + main = "src/index.ts" 3 + compatibility_date = "2026-04-28" 4 + 5 + [[rules]] 6 + type = "Text" 7 + globs = ["**/*.md"] 8 + 9 + [vars] 10 + LINEAR_TEAM_ID = "4be92e1b-1c19-4d37-ab9f-38c7484a00af" 11 + 12 + # Set secrets via: wrangler secret put LINEAR_API_KEY 13 + # LINEAR_API_KEY = "lin_api_..." 14 + 15 + [[routes]] 16 + pattern = "jobs.tangled.org" 17 + zone_name = "tangled.org" 18 + custom_domain = true 19 + 20 + [observability] 21 + [observability.logs] 22 + enabled = true 23 + invocation_logs = true