commit 210690698b4f330479558aa5b8bf143f5a12e880 Author: Puck Meerburg Date: Thu Apr 30 12:13:22 2026 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..f7e05ef --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "trailingComma": "es5", + "tabWidth": 2, + "bracketSameLine": true +} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..080049d --- /dev/null +++ b/bun.lock @@ -0,0 +1,229 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "microtrmnl", + "dependencies": { + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "bun2nix": "^2.0.8", + "node-ical": "^0.26.0", + "puppeteer-core": "^24.42.0", + "react": "^19.2.5", + "react-dom": "^19.2.5", + }, + "devDependencies": { + "@types/bun": "latest", + "prettier": "^3.8.3", + "typescript-language-server": "^5.1.3", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="], + + "@puppeteer/browsers": ["@puppeteer/browsers@2.13.0", "", { "dependencies": { "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.4", "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA=="], + + "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], + + "@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="], + + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], + + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], + + "b4a": ["b4a@1.8.0", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg=="], + + "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="], + + "bare-fs": ["bare-fs@4.7.1", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw=="], + + "bare-os": ["bare-os@3.8.7", "", {}, "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w=="], + + "bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="], + + "bare-stream": ["bare-stream@2.13.0", "", { "dependencies": { "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-abort-controller", "bare-buffer", "bare-events"] }, "sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA=="], + + "bare-url": ["bare-url@2.4.1", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-fZapLWNB25gS+etK27NV9KgBNXgo2yeYHuj+OyPblQd6GYAE3JVy6aKxszMV5jhGGFwraXQKA5fldvf3lMyEqw=="], + + "basic-ftp": ["basic-ftp@5.3.0", "", {}, "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], + + "bun2nix": ["bun2nix@2.0.8", "", { "dependencies": { "sade": "^1.8.1" }, "bin": { "bun2nix": "index.ts" } }, "sha512-pwq35hA81X1Kjsi5Xo69Aii9aY3zZHhWXwqF1QRz/uB35KzKbwJZ16WrhadiG9/T6bjOsRrPZtzuAyqmlXopLw=="], + + "chromium-bidi": ["chromium-bidi@14.0.0", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], + + "devtools-protocol": ["devtools-protocol@0.0.1595872", "", {}, "sha512-kRfgp8vWVjBu/fbYCiVFiOqsCk3CrMKEo3WbgGT2NXK2dG7vawWPBljixajVgGK9II8rDO9G0oD0zLt3I1daRg=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], + + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], + + "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], + + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + + "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "jsbi": ["jsbi@4.3.2", "", {}, "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="], + + "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], + + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="], + + "node-ical": ["node-ical@0.26.0", "", { "dependencies": { "rrule-temporal": "^1.5.1", "temporal-polyfill": "^0.3.2" } }, "sha512-tJZY2fMb38Gbj0P05zHMWBr90MslhGZ1qEbOWYnokBYPPX/lYskL/0NnWoeiXTBNod+kRRcTOjxAeB20kfvKyw=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], + + "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], + + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], + + "prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="], + + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + + "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + + "puppeteer-core": ["puppeteer-core@24.42.0", "", { "dependencies": { "@puppeteer/browsers": "2.13.0", "chromium-bidi": "14.0.0", "debug": "^4.4.3", "devtools-protocol": "0.0.1595872", "typed-query-selector": "^2.12.1", "webdriver-bidi-protocol": "0.4.1", "ws": "^8.19.0" } }, "sha512-T4zXokk/izH01fYPhyyev1A4piWiOKrYq7CUFpdoYQxmOnXoV6YjUabmfIjCYkNspSoAXIxRid3Tw+Vg0fthYg=="], + + "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="], + + "react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "rrule-temporal": ["rrule-temporal@1.5.2", "", { "dependencies": { "@js-temporal/polyfill": "^0.5.1" } }, "sha512-I5rAiZfRlMh0vuG23HrGBMLZOSiQO7H1Uq8l9qyfA6oTD5j+UMRwpRs4aVU4XdaFhgN1p3K+cHelG8KvLTTm+g=="], + + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "streamx": ["streamx@2.25.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "tar-fs": ["tar-fs@3.1.2", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw=="], + + "tar-stream": ["tar-stream@3.1.8", "", { "dependencies": { "b4a": "^1.6.4", "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ=="], + + "teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "^2.12.5" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="], + + "temporal-polyfill": ["temporal-polyfill@0.3.2", "", { "dependencies": { "temporal-spec": "0.3.1" } }, "sha512-TzHthD/heRK947GNiSu3Y5gSPpeUDH34+LESnfsq8bqpFhsB79HFBX8+Z834IVX68P3EUyRPZK5bL/1fh437Eg=="], + + "temporal-spec": ["temporal-spec@0.3.1", "", {}, "sha512-B4TUhezh9knfSIMwt7RVggApDRJZo73uZdj8AacL2mZ8RP5KtLianh2MXxL06GN9ESYiIsiuoLQhgVfwe55Yhw=="], + + "text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typed-query-selector": ["typed-query-selector@2.12.1", "", {}, "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "typescript-language-server": ["typescript-language-server@5.1.3", "", { "bin": { "typescript-language-server": "lib/cli.mjs" } }, "sha512-r+pAcYtWdN8tKlYZPwiiHNA2QPjXnI02NrW5Sf2cVM3TRtuQ3V9EKKwOxqwaQ0krsaEXk/CbN90I5erBuf84Vg=="], + + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + + "webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.4.1", "", {}, "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw=="], + + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + } +} diff --git a/bun.nix b/bun.nix new file mode 100644 index 0000000..0352341 --- /dev/null +++ b/bun.nix @@ -0,0 +1,420 @@ +# Autogenerated by `bun2nix`, editing manually is not recommended +# +# Set of Bun packages to install +# +# Consume this with `fetchBunDeps` (recommended) +# or `pkgs.callPackage` if you wish to handle +# it manually. +{ + copyPathToStore, + fetchFromGitHub, + fetchgit, + fetchurl, + ... +}: +{ + "@js-temporal/polyfill@0.5.1" = fetchurl { + url = "https://registry.npmjs.org/@js-temporal/polyfill/-/polyfill-0.5.1.tgz"; + hash = "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="; + }; + "@puppeteer/browsers@2.13.0" = fetchurl { + url = "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz"; + hash = "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA=="; + }; + "@tootallnate/quickjs-emscripten@0.23.0" = fetchurl { + url = "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz"; + hash = "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="; + }; + "@types/bun@1.3.12" = fetchurl { + url = "https://registry.npmjs.org/@types/bun/-/bun-1.3.12.tgz"; + hash = "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="; + }; + "@types/node@25.6.0" = fetchurl { + url = "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz"; + hash = "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="; + }; + "@types/react-dom@19.2.3" = fetchurl { + url = "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz"; + hash = "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="; + }; + "@types/react@19.2.14" = fetchurl { + url = "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz"; + hash = "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="; + }; + "@types/yauzl@2.10.3" = fetchurl { + url = "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz"; + hash = "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="; + }; + "agent-base@7.1.4" = fetchurl { + url = "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz"; + hash = "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="; + }; + "ansi-regex@5.0.1" = fetchurl { + url = "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz"; + hash = "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="; + }; + "ansi-styles@4.3.0" = fetchurl { + url = "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"; + hash = "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="; + }; + "ast-types@0.13.4" = fetchurl { + url = "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz"; + hash = "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="; + }; + "b4a@1.8.0" = fetchurl { + url = "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz"; + hash = "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg=="; + }; + "bare-events@2.8.2" = fetchurl { + url = "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz"; + hash = "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="; + }; + "bare-fs@4.7.1" = fetchurl { + url = "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz"; + hash = "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw=="; + }; + "bare-os@3.8.7" = fetchurl { + url = "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz"; + hash = "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w=="; + }; + "bare-path@3.0.0" = fetchurl { + url = "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz"; + hash = "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="; + }; + "bare-stream@2.13.0" = fetchurl { + url = "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.0.tgz"; + hash = "sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA=="; + }; + "bare-url@2.4.1" = fetchurl { + url = "https://registry.npmjs.org/bare-url/-/bare-url-2.4.1.tgz"; + hash = "sha512-fZapLWNB25gS+etK27NV9KgBNXgo2yeYHuj+OyPblQd6GYAE3JVy6aKxszMV5jhGGFwraXQKA5fldvf3lMyEqw=="; + }; + "basic-ftp@5.3.0" = fetchurl { + url = "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.0.tgz"; + hash = "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w=="; + }; + "buffer-crc32@0.2.13" = fetchurl { + url = "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz"; + hash = "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="; + }; + "bun-types@1.3.12" = fetchurl { + url = "https://registry.npmjs.org/bun-types/-/bun-types-1.3.12.tgz"; + hash = "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="; + }; + "bun2nix@2.0.8" = fetchurl { + url = "https://registry.npmjs.org/bun2nix/-/bun2nix-2.0.8.tgz"; + hash = "sha512-pwq35hA81X1Kjsi5Xo69Aii9aY3zZHhWXwqF1QRz/uB35KzKbwJZ16WrhadiG9/T6bjOsRrPZtzuAyqmlXopLw=="; + }; + "chromium-bidi@14.0.0" = fetchurl { + url = "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz"; + hash = "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw=="; + }; + "cliui@8.0.1" = fetchurl { + url = "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz"; + hash = "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="; + }; + "color-convert@2.0.1" = fetchurl { + url = "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"; + hash = "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="; + }; + "color-name@1.1.4" = fetchurl { + url = "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"; + hash = "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="; + }; + "csstype@3.2.3" = fetchurl { + url = "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz"; + hash = "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="; + }; + "data-uri-to-buffer@6.0.2" = fetchurl { + url = "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz"; + hash = "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="; + }; + "debug@4.4.3" = fetchurl { + url = "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"; + hash = "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="; + }; + "degenerator@5.0.1" = fetchurl { + url = "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz"; + hash = "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="; + }; + "devtools-protocol@0.0.1595872" = fetchurl { + url = "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1595872.tgz"; + hash = "sha512-kRfgp8vWVjBu/fbYCiVFiOqsCk3CrMKEo3WbgGT2NXK2dG7vawWPBljixajVgGK9II8rDO9G0oD0zLt3I1daRg=="; + }; + "emoji-regex@8.0.0" = fetchurl { + url = "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"; + hash = "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="; + }; + "end-of-stream@1.4.5" = fetchurl { + url = "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz"; + hash = "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="; + }; + "escalade@3.2.0" = fetchurl { + url = "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"; + hash = "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="; + }; + "escodegen@2.1.0" = fetchurl { + url = "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz"; + hash = "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="; + }; + "esprima@4.0.1" = fetchurl { + url = "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz"; + hash = "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="; + }; + "estraverse@5.3.0" = fetchurl { + url = "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"; + hash = "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="; + }; + "esutils@2.0.3" = fetchurl { + url = "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"; + hash = "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="; + }; + "events-universal@1.0.1" = fetchurl { + url = "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz"; + hash = "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="; + }; + "extract-zip@2.0.1" = fetchurl { + url = "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz"; + hash = "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="; + }; + "fast-fifo@1.3.2" = fetchurl { + url = "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz"; + hash = "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="; + }; + "fd-slicer@1.1.0" = fetchurl { + url = "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz"; + hash = "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="; + }; + "get-caller-file@2.0.5" = fetchurl { + url = "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"; + hash = "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="; + }; + "get-stream@5.2.0" = fetchurl { + url = "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz"; + hash = "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="; + }; + "get-uri@6.0.5" = fetchurl { + url = "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz"; + hash = "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="; + }; + "http-proxy-agent@7.0.2" = fetchurl { + url = "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz"; + hash = "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="; + }; + "https-proxy-agent@7.0.6" = fetchurl { + url = "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz"; + hash = "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="; + }; + "ip-address@10.1.0" = fetchurl { + url = "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz"; + hash = "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="; + }; + "is-fullwidth-code-point@3.0.0" = fetchurl { + url = "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"; + hash = "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="; + }; + "jsbi@4.3.2" = fetchurl { + url = "https://registry.npmjs.org/jsbi/-/jsbi-4.3.2.tgz"; + hash = "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="; + }; + "lru-cache@7.18.3" = fetchurl { + url = "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz"; + hash = "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="; + }; + "mitt@3.0.1" = fetchurl { + url = "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz"; + hash = "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="; + }; + "mri@1.2.0" = fetchurl { + url = "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz"; + hash = "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="; + }; + "ms@2.1.3" = fetchurl { + url = "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"; + hash = "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="; + }; + "netmask@2.1.1" = fetchurl { + url = "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz"; + hash = "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="; + }; + "node-ical@0.26.0" = fetchurl { + url = "https://registry.npmjs.org/node-ical/-/node-ical-0.26.0.tgz"; + hash = "sha512-tJZY2fMb38Gbj0P05zHMWBr90MslhGZ1qEbOWYnokBYPPX/lYskL/0NnWoeiXTBNod+kRRcTOjxAeB20kfvKyw=="; + }; + "once@1.4.0" = fetchurl { + url = "https://registry.npmjs.org/once/-/once-1.4.0.tgz"; + hash = "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="; + }; + "pac-proxy-agent@7.2.0" = fetchurl { + url = "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz"; + hash = "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="; + }; + "pac-resolver@7.0.1" = fetchurl { + url = "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz"; + hash = "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="; + }; + "pend@1.2.0" = fetchurl { + url = "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz"; + hash = "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="; + }; + "prettier@3.8.3" = fetchurl { + url = "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz"; + hash = "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="; + }; + "progress@2.0.3" = fetchurl { + url = "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"; + hash = "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="; + }; + "proxy-agent@6.5.0" = fetchurl { + url = "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz"; + hash = "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="; + }; + "proxy-from-env@1.1.0" = fetchurl { + url = "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"; + hash = "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="; + }; + "pump@3.0.4" = fetchurl { + url = "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz"; + hash = "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="; + }; + "puppeteer-core@24.42.0" = fetchurl { + url = "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.42.0.tgz"; + hash = "sha512-T4zXokk/izH01fYPhyyev1A4piWiOKrYq7CUFpdoYQxmOnXoV6YjUabmfIjCYkNspSoAXIxRid3Tw+Vg0fthYg=="; + }; + "react-dom@19.2.5" = fetchurl { + url = "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz"; + hash = "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="; + }; + "react@19.2.5" = fetchurl { + url = "https://registry.npmjs.org/react/-/react-19.2.5.tgz"; + hash = "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="; + }; + "require-directory@2.1.1" = fetchurl { + url = "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"; + hash = "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="; + }; + "rrule-temporal@1.5.2" = fetchurl { + url = "https://registry.npmjs.org/rrule-temporal/-/rrule-temporal-1.5.2.tgz"; + hash = "sha512-I5rAiZfRlMh0vuG23HrGBMLZOSiQO7H1Uq8l9qyfA6oTD5j+UMRwpRs4aVU4XdaFhgN1p3K+cHelG8KvLTTm+g=="; + }; + "sade@1.8.1" = fetchurl { + url = "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz"; + hash = "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="; + }; + "scheduler@0.27.0" = fetchurl { + url = "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz"; + hash = "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="; + }; + "semver@7.7.4" = fetchurl { + url = "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz"; + hash = "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="; + }; + "smart-buffer@4.2.0" = fetchurl { + url = "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz"; + hash = "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="; + }; + "socks-proxy-agent@8.0.5" = fetchurl { + url = "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz"; + hash = "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="; + }; + "socks@2.8.7" = fetchurl { + url = "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz"; + hash = "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="; + }; + "source-map@0.6.1" = fetchurl { + url = "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"; + hash = "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="; + }; + "streamx@2.25.0" = fetchurl { + url = "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz"; + hash = "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg=="; + }; + "string-width@4.2.3" = fetchurl { + url = "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"; + hash = "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="; + }; + "strip-ansi@6.0.1" = fetchurl { + url = "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"; + hash = "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="; + }; + "tar-fs@3.1.2" = fetchurl { + url = "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz"; + hash = "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw=="; + }; + "tar-stream@3.1.8" = fetchurl { + url = "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz"; + hash = "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ=="; + }; + "teex@1.0.1" = fetchurl { + url = "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz"; + hash = "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="; + }; + "temporal-polyfill@0.3.2" = fetchurl { + url = "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.3.2.tgz"; + hash = "sha512-TzHthD/heRK947GNiSu3Y5gSPpeUDH34+LESnfsq8bqpFhsB79HFBX8+Z834IVX68P3EUyRPZK5bL/1fh437Eg=="; + }; + "temporal-spec@0.3.1" = fetchurl { + url = "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.3.1.tgz"; + hash = "sha512-B4TUhezh9knfSIMwt7RVggApDRJZo73uZdj8AacL2mZ8RP5KtLianh2MXxL06GN9ESYiIsiuoLQhgVfwe55Yhw=="; + }; + "text-decoder@1.2.7" = fetchurl { + url = "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz"; + hash = "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="; + }; + "tslib@2.8.1" = fetchurl { + url = "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"; + hash = "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="; + }; + "typed-query-selector@2.12.1" = fetchurl { + url = "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.1.tgz"; + hash = "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA=="; + }; + "typescript-language-server@5.1.3" = fetchurl { + url = "https://registry.npmjs.org/typescript-language-server/-/typescript-language-server-5.1.3.tgz"; + hash = "sha512-r+pAcYtWdN8tKlYZPwiiHNA2QPjXnI02NrW5Sf2cVM3TRtuQ3V9EKKwOxqwaQ0krsaEXk/CbN90I5erBuf84Vg=="; + }; + "typescript@5.9.3" = fetchurl { + url = "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz"; + hash = "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="; + }; + "undici-types@7.19.2" = fetchurl { + url = "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz"; + hash = "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="; + }; + "webdriver-bidi-protocol@0.4.1" = fetchurl { + url = "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz"; + hash = "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw=="; + }; + "wrap-ansi@7.0.0" = fetchurl { + url = "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"; + hash = "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="; + }; + "wrappy@1.0.2" = fetchurl { + url = "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"; + hash = "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="; + }; + "ws@8.20.0" = fetchurl { + url = "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz"; + hash = "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="; + }; + "y18n@5.0.8" = fetchurl { + url = "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"; + hash = "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="; + }; + "yargs-parser@21.1.1" = fetchurl { + url = "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"; + hash = "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="; + }; + "yargs@17.7.2" = fetchurl { + url = "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz"; + hash = "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="; + }; + "yauzl@2.10.0" = fetchurl { + url = "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz"; + hash = "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="; + }; + "zod@3.25.76" = fetchurl { + url = "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz"; + hash = "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="; + }; +} diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..e9b0e26 --- /dev/null +++ b/default.nix @@ -0,0 +1,77 @@ +{ + pkgs, + lib, + config, + bun2nix, + ... +}: let + # This code, when _compiled/minified_, seems to break. + # I'm not debugging this right now. + server = pkgs.stdenv.mkDerivation { + pname = "trmnlc"; + version = "1.0.0"; + src = ./.; + nativeBuildInputs = [ bun2nix.hook ]; + + bunDeps = bun2nix.fetchBunDeps { + bunNix = ./bun.nix; + }; + + buildPhase = "true"; + installPhase = '' + cp -rf . $out + ''; + }; + + cfg = config.services.trmnlc; + json = pkgs.formats.json {}; + configFile = json.generate "config.json" cfg.settings; +in +{ + options.services.trmnlc = { + enable = lib.mkEnableOption "TRMNLc server software"; + settings = lib.mkOption { + default = {}; + type = json.type; + example = lib.literalExpression '' + { + unknown = { + urls = ["https://user.fm/calendar/....ics"]; + refresh = 60; + model = "inkplate_10"; + }; + } + ''; + + description = "The TRMNLc configuration."; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.trmnl = { + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.imagemagick ]; + + environment = { + FIREFOX = "${pkgs.firefox}/bin/firefox"; + CONFIG_FILE = configFile; + HOME = "/tmp"; + TZ = "Europe/Amsterdam"; + FONTCONFIG_FILE = ./fonts.conf; + COLORMAP = ./colormap.png; + BUN_PORT = "2300"; + }; + + serviceConfig = { + ExecStart = "${pkgs.bun}/bin/bun run ${server}/src/web.ts"; + KillMode = "mixed"; + KillSignal = "SIGKILL"; + Restart = "on-failure"; + RestartSec = 5; + RestartPreventExitStatus = "SIGKILL"; + + DynamicUser = true; + }; + }; + }; +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..20796fe --- /dev/null +++ b/flake.nix @@ -0,0 +1,15 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + bun2nix.url = "github:nix-community/bun2nix"; + bun2nix.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = { + self, + nixpkgs, + bun2nix, + }: { + nixosModules.default = ./default.nix; + }; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2b513bc --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "microtrmnl", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest", + "prettier": "^3.8.3", + "typescript-language-server": "^5.1.3" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "bun2nix": "^2.0.8", + "node-ical": "^0.26.0", + "puppeteer-core": "^24.42.0", + "react": "^19.2.5", + "react-dom": "^19.2.5" + } +} diff --git a/src/devices.ts b/src/devices.ts new file mode 100644 index 0000000..4f38f22 --- /dev/null +++ b/src/devices.ts @@ -0,0 +1,40 @@ +export interface DeviceModel { + name: string; + label: string; + description: string; + width: number; + height: number; + colors: number; + bit_depth: number; + scale_factor: number; + rotation: number; + mime_type: string; + offset_x: number; + offset_y: number; + kind: string; + palette_ids: string[]; + preview_white_point: string; + image_size_limit: number; + image_upload_supported: boolean; + css: { + classes: { + device: string; + size: string; + density: string; + }; + + variables: [string, string][]; + }; +} + +export const devices: DeviceModel[] = ( + (await ( + await fetch( + (globalThis as any).document + ? '/api/models' + : 'https://trmnl.com/api/models' + ) + ).json()) as { + data: DeviceModel[]; + } +).data; diff --git a/src/render.ts b/src/render.ts new file mode 100644 index 0000000..d0a22a8 --- /dev/null +++ b/src/render.ts @@ -0,0 +1,46 @@ +import puppeteer from 'puppeteer-core'; +import { type DeviceModel } from './devices'; +import { render, type PluginList } from './template'; +import { sleep } from 'bun'; +import sharp from 'sharp'; + +const p = await puppeteer.launch({ + browser: 'firefox', + args: ['--new-instance'], + executablePath: Bun.env['FIREFOX'], + dumpio: true, +}); + +export const makeScreenshot = async ( + drawable: PluginList, + model: DeviceModel +): Promise => { + const page = await p.newPage({}); + await page.setViewport({ + width: model.width, + height: model.height, + deviceScaleFactor: 1, + }); + await page.setContent(await render(drawable, model)); + await sleep(2000); + const screenshot = await page.screenshot({ encoding: 'binary' }); + await page.close(); + const processed = await Bun.spawn( + [ + 'magick', + '-', + '-dither', + 'FloydSteinberg', + '-remap', + Bun.env['COLORMAP'], + '-define', + 'png:bit-depth=2', + '-define', + 'png:color-type=0', + '-strip', + 'png:-', + ], + { stdin: screenshot } + ).stdout.bytes(); + return processed; +}; diff --git a/src/template.ts b/src/template.ts new file mode 100644 index 0000000..2f0b668 --- /dev/null +++ b/src/template.ts @@ -0,0 +1,39 @@ +import { devices, type DeviceModel } from './devices'; + +const BASE = ` + + + + + + + + + + +
{{contents}}
+ + +`; + +export type RenderMode = + | 'full' + | 'half_horizontal' + | 'half_vertical' + | 'quadrant'; +export interface Renderable { + render(mode: RenderMode, model: DeviceModel): Promise; +} + +export type PluginList = [Renderable, RenderMode][]; + +export const render = async (plugins: PluginList, model: DeviceModel) => { + const classes = Object.values(model.css.classes).join(' '); + + let contents = ''; + for (let [plugin, mode] of plugins) { + contents += await plugin.render(mode, model); + } + + return BASE.replace('{{classes}}', classes).replace('{{contents}}', contents); +}; diff --git a/src/web.ts b/src/web.ts new file mode 100644 index 0000000..3171c4f --- /dev/null +++ b/src/web.ts @@ -0,0 +1,112 @@ +import { SHA256 } from 'bun'; +import { devices } from './devices'; +import { makeScreenshot } from './render'; +import { render } from './template'; +import { ICSRenderable } from './xlcalendar'; + +let renderMap = new Map(); + +interface Configuration { + [mac: string]: { + urls: string[]; + refresh?: number; + model: string; + }; +} + +const config: Configuration = JSON.parse( + await Bun.file(Bun.env['CONFIG_FILE'] ?? 'config.json').text() +); + +Bun.serve({ + hostname: '0.0.0.0', + + routes: { + '/api/setup': async (req) => { + const id = req.headers.get('ID') ?? 'UNKNOWN'; + const newfriendly = id.replaceAll(':', ''); + + return Response.json({ + status: 200, + message: 'hi', + api_key: id, + friendly_id: newfriendly, + image_url: 'https://trmnl.com/images/setup/setup-logo.bmp', + filename: 'empty_state', + }); + }, + + '/api/display': async (req) => { + const id = req.headers.get('ID') ?? 'unknown'; + const newfriendly = id.replaceAll(':', ''); + + const device = config[id] ?? { + urls: [], + refresh: undefined, + model: 'inkplate_10', + }; + + console.log(`Rendering for ${id}..`); + + let plugin = new ICSRenderable(device.urls); + const rendered = makeScreenshot( + [[plugin, 'full']], + devices.find((a) => a.name === device.model)! + ); + renderMap.set(id.toLowerCase(), rendered); + + await Bun.sleep(3500); + + let nextRefresh = device.refresh ?? 5 * 60; + if (plugin.nextEvent) { + let timeUntilNextEvent = + (plugin.nextEvent.valueOf() - Date.now()) / 1000; + if (timeUntilNextEvent < 60) timeUntilNextEvent = 60; + if (device.refresh && timeUntilNextEvent > device.refresh) + timeUntilNextEvent = device.refresh; + + if (timeUntilNextEvent < nextRefresh) nextRefresh = timeUntilNextEvent; + } + + if (!plugin.hash) { + console.log(`Had to respond before plugin for ${id} was rendered.`); + } + + return Response.json({ + status: 0, + image_url: `http://192.168.50.124:2300/api/render/${id.toLowerCase()}/${plugin.hash ?? Math.random()}.png`, + refresh_rate: nextRefresh.toString(), + update_firmware: false, + firmware_url: null, + reset_firmware: false, + filename: `${plugin.hash ?? Math.random()}.png`, + }); + }, + + '/api/display/html': async (req) => { + const id = req.headers.get('ID') ?? 'unknown'; + const newfriendly = id.replaceAll(':', ''); + + const device = config[id] ?? { + urls: [], + refresh: undefined, + model: 'inkplate_10', + }; + + let plugin = new ICSRenderable(device.urls); + const rendered = await render( + [[plugin, 'full']], + devices.find((a) => a.name === device.model)! + ); + return new Response(rendered, { + headers: { 'Content-Type': 'text/html' }, + }); + }, + + '/api/render/:id/:ignore': async (req) => { + return new Response(await renderMap.get(req.params.id), { + headers: { 'Content-Type': 'image/png' }, + }); + }, + }, +}); diff --git a/src/xlcalendar.tsx b/src/xlcalendar.tsx new file mode 100644 index 0000000..5b598cb --- /dev/null +++ b/src/xlcalendar.tsx @@ -0,0 +1,383 @@ +import { renderToString } from 'react-dom/server'; + +import * as ical from 'node-ical'; +import type { Renderable, RenderMode } from '../src/template'; +import type { DeviceModel } from '../src/devices'; + +type ProcessedEvent = ical.EventInstance & { calname: string }; + +const SectionLabel = ({ + text, + meta, + inverted = false, +}: { + text: string; + meta?: string; + inverted?: boolean; +}) => { + const classes = inverted + ? 'label lg:label--large label--underline' + : 'label lg:label--large label--filled'; + return ( +
+ {text} + {meta && {meta}} +
+ ); +}; + +const TitleBar = ({ + title, + instance, +}: { + title: string; + instance?: string; +}) => { + return ( +
+ {title} + {instance && {instance}} +
+ ); +}; + +interface Data { + current_event?: ProcessedEvent; + next_event?: ProcessedEvent; + secondary_event?: ProcessedEvent; + concurrent_event_count: number; + upcoming_count: number; +} + +const processCalendar = (cal: ProcessedEvent[]): Data => { + const data: Data = { concurrent_event_count: 0, upcoming_count: 0 }; + const now = Date.now(); + + for (let event of cal) { + if (event.start.valueOf() > now) data.upcoming_count++; + if (event.isFullDay) continue; + + if (event.start.valueOf() <= now && event.end.valueOf() > now) { + if (data.current_event) data.concurrent_event_count++; + else { + data.current_event = event; + } + } + + if (!data.next_event && event.start.valueOf() > now) { + data.next_event = event; + } else if ( + !data.secondary_event && + event.start.valueOf() > now && + data.next_event + ) { + data.secondary_event = event; + } + + if ( + data.current_event && + data.next_event && + data.secondary_event && + event.start.valueOf() > now + ) + break; + } + + return data; +}; + +const getTimeLabel = (event: ProcessedEvent): string => { + let now = new Date(Date.now()); + + if ( + event.start.toDateString() == event.end.toDateString() && + now.toDateString() == event.start.toDateString() + ) + return ( + event.start.toLocaleTimeString('nl-NL', { timeStyle: 'short' }) + + ' - ' + + event.end.toLocaleTimeString('nl-NL', { timeStyle: 'short' }) + ); + else + return ( + event.start.toLocaleDateString('nl-NL', { dateStyle: 'medium' }) + + (event.isFullDay + ? '' + : ' om ' + + event.start.toLocaleTimeString('nl-NL', { timeStyle: 'short' })) + ); +}; + +// title bar shows either 'FREE NOW' or 'BUSY' (optionally 'BUSY · {upcoming_count} MORE') +const HeroPanel = ({ + event, + now, + concurrent, +}: { + event?: ProcessedEvent; + now: boolean; + concurrent: number; +}) => { + // show current event if one, otherwise show next + // hero panel doesn't care about this but + + const primaryLabel = event && now ? 'NU' : 'VOLGENDE'; + let timeLabel = event ? getTimeLabel(event) : ''; + if (concurrent) timeLabel += ` + ${concurrent} meer`; + + if (event) { + return ( +
+ + + + {event?.summary as string} + + + + +
+ ); + } else { + return ( +
+
VRIJ
+
Geen events vandaag
+
+ ); + } +}; + +const NextEvent = ({ + event, + hasNow, +}: { + event?: ProcessedEvent; + hasNow: boolean; +}) => { + let ev; + if (event) + ev = ( + <> +
+ {event.summary as string} +
+
+ {getTimeLabel(event)} +
+ + ); + else + ev = ( + <> +
VRIJ
+
Geen events meer hierna.
+ + ); + + return ( +
+ + {ev} +
+ ); +}; + +const SmallRenderer = ({ event }: { event: ProcessedEvent }) => { + const date = event.start + .toLocaleDateString('nl-NL', { dateStyle: 'medium' }) + .replace(' ' + event.start.getFullYear().toString(), ''); + const time = event.start.toLocaleTimeString('nl-NL', { timeStyle: 'short' }); + + return ( +
+
+
+ + {event.summary as string} + + + {time} · {date} + +
+
+ ); +}; + +const LaterEvents = ({ + data, + events, +}: { + data: Data; + events: ProcessedEvent[]; +}) => { + let eligibleEvents = []; + let now = Date.now(); + for (let event of events) { + if ( + event === data.current_event || + event === data.next_event || + event.isFullDay + ) + continue; + + const eligible_ts = event.isFullDay ? event.end : event.start; + if (eligible_ts.valueOf() > now) { + eligibleEvents.push( + + ); + if (eligibleEvents.length > 2) break; + } + } + + return ( +
+ + +
+ {eligibleEvents.length ? eligibleEvents : null} + {eligibleEvents.length ? null : ( +
Niks anders gepland.
+ )} +
+
+ ); +}; + +const Main = ({ data, events }: { data: Data; events: ProcessedEvent[] }) => { + let titlebarFlag = 'VRIJ'; + if (data.current_event) { + titlebarFlag = 'BEZET'; + + if (data.upcoming_count) titlebarFlag += ` · ${data.upcoming_count} MEER`; + } + const now = new Date(Date.now()).toLocaleString('nl-NL', { + dateStyle: 'medium', + timeStyle: 'short', + }); + return ( + <> +
+
+ +
+ + +
+
+
+ + + ); +}; + +export class ICSRenderable implements Renderable { + public nextEvent?: Date; + public hash: string = ''; + + constructor(private files: string[]) {} + + private async fetch( + url: string, + start: Date, + end: Date + ): Promise { + const resp = await fetch(url, { headers: [['User-Agent', 'private TRMNL fetcher']] }); + let data = await resp.text(); + let events = Object.values(await ical.async.parseICS(data)).filter( + (a) => a?.type === 'VEVENT' + ); + let processed: ProcessedEvent[] = []; + for (let event of events) { + for (let subevent of ical.expandRecurringEvent(event, { + from: start, + to: end, + excludeExdates: true, + expandOngoing: true, + includeOverrides: true, + })) { + processed.push({ ...subevent, calname: 'Test' }); + } + } + + return processed; + } + + async render(_mode: RenderMode): Promise { + const start = new Date(Date.now()); + const end = new Date(start.valueOf()); + end.setDate(start.getDate() + 7); + let events = ( + await Promise.all(this.files.map((a) => this.fetch(a, start, end))) + ).flat(); + events.sort((a, b) => a.start.valueOf() - b.start.valueOf()); + + let data = processCalendar(events); + + const now = new Date(Date.now()).toLocaleString('nl-NL', { + dateStyle: 'medium', + timeStyle: 'short', + }); + + this.hash = Bun.hash + .xxHash64( + JSON.stringify([ + data.current_event?.summary, + data.next_event?.summary, + data.current_event?.start.toString(), + now.substring(0, now.length - 1) + ]), + 2n + ) + .toString(); + + this.nextEvent = (data.next_event ?? data.secondary_event)?.start; + + return ( + '
' + + renderToString(
) + + '
' + ); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bb6d388 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false, + + "jsx": "react-jsx" + } +}