Initial commit
This commit is contained in:
commit
210690698b
13 changed files with 1454 additions and 0 deletions
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
|
|
@ -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
|
||||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"bracketSameLine": true
|
||||||
|
}
|
||||||
229
bun.lock
Normal file
229
bun.lock
Normal file
|
|
@ -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=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
420
bun.nix
Normal file
420
bun.nix
Normal file
|
|
@ -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==";
|
||||||
|
};
|
||||||
|
}
|
||||||
77
default.nix
Normal file
77
default.nix
Normal file
|
|
@ -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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
15
flake.nix
Normal file
15
flake.nix
Normal file
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
23
package.json
Normal file
23
package.json
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/devices.ts
Normal file
40
src/devices.ts
Normal file
|
|
@ -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;
|
||||||
46
src/render.ts
Normal file
46
src/render.ts
Normal file
|
|
@ -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<Uint8Array> => {
|
||||||
|
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;
|
||||||
|
};
|
||||||
39
src/template.ts
Normal file
39
src/template.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { devices, type DeviceModel } from './devices';
|
||||||
|
|
||||||
|
const BASE = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;350;375;400;450;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link href="https://trmnl.com/css/3.0.3/plugins.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="environment trmnl">
|
||||||
|
<div class="screen screen--1bit {{classes}}" data-pixel-perfect="true">{{contents}}</div>
|
||||||
|
<script src="https://trmnl.com/js/3.0.3/plugins.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
export type RenderMode =
|
||||||
|
| 'full'
|
||||||
|
| 'half_horizontal'
|
||||||
|
| 'half_vertical'
|
||||||
|
| 'quadrant';
|
||||||
|
export interface Renderable {
|
||||||
|
render(mode: RenderMode, model: DeviceModel): Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
112
src/web.ts
Normal file
112
src/web.ts
Normal file
|
|
@ -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' },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
383
src/xlcalendar.tsx
Normal file
383
src/xlcalendar.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<div className="flex flex--row flex--between flex--center-y gap--xsmall">
|
||||||
|
<span className={classes}>{text}</span>
|
||||||
|
{meta && <span className={classes}>{meta}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TitleBar = ({
|
||||||
|
title,
|
||||||
|
instance,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
instance?: string;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="title_bar">
|
||||||
|
<span className="title">{title}</span>
|
||||||
|
{instance && <span className="instance">{instance}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="rounded--large p--2 flex flex--col gap--small h--[60cqh] bg--black text--white">
|
||||||
|
<SectionLabel text={primaryLabel} meta={timeLabel} inverted />
|
||||||
|
<span className={now ? "bg--black rounded p--[4cqw] " : ""}>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
(now ? 'text--white' : 'text--black') +
|
||||||
|
' rounded p--[4cqw] value value--xxxlarge text--center block lg:hidden'
|
||||||
|
}
|
||||||
|
data-value-fit
|
||||||
|
data-value-fit-max-height="150">
|
||||||
|
{event?.summary as string}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
(now ? 'text--white' : 'text--black') +
|
||||||
|
' rounded p--[4cqw] value value--xxxlarge text--center hidden lg:block lg:portrait:hidden'
|
||||||
|
}
|
||||||
|
data-value-fit
|
||||||
|
data-value-fit-max-height="320">
|
||||||
|
{event?.summary as string}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
(now ? 'text--white' : 'text--black') +
|
||||||
|
' value value--xxxlarge text--center hidden lg:portrait:block'
|
||||||
|
}
|
||||||
|
data-value-fit
|
||||||
|
data-value-fit-max-height="480">
|
||||||
|
{event?.summary as string}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div className="rounded--large p--2 flex flex--col gap--small h--[60cqh]">
|
||||||
|
<div className="value value--xxxlarge">VRIJ</div>
|
||||||
|
<div className="title title--small">Geen events vandaag</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const NextEvent = ({
|
||||||
|
event,
|
||||||
|
hasNow,
|
||||||
|
}: {
|
||||||
|
event?: ProcessedEvent;
|
||||||
|
hasNow: boolean;
|
||||||
|
}) => {
|
||||||
|
let ev;
|
||||||
|
if (event)
|
||||||
|
ev = (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="value text--center md:portrait:value--small lg:value--large lg:portrait:value--large"
|
||||||
|
data-clamp="2">
|
||||||
|
{event.summary as string}
|
||||||
|
</div>
|
||||||
|
<div className="value text--center value--small md:value--medium lg:value">
|
||||||
|
{getTimeLabel(event)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
else
|
||||||
|
ev = (
|
||||||
|
<>
|
||||||
|
<div className="title title--small">VRIJ</div>
|
||||||
|
<div className="description">Geen events meer hierna.</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="outline rounded--large p--2 flex flex--col gap--small">
|
||||||
|
<SectionLabel text={hasNow ? 'VOLGENDE' : 'DAARNA'} inverted />
|
||||||
|
{ev}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="item">
|
||||||
|
<div className="meta"></div>
|
||||||
|
<div className="content">
|
||||||
|
<span
|
||||||
|
className="title title--small md:title--medium lg:title--large"
|
||||||
|
data-clamp="1">
|
||||||
|
{event.summary as string}
|
||||||
|
</span>
|
||||||
|
<span className="label label--small lg:label--base lg:label--outline">
|
||||||
|
{time} · {date}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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(
|
||||||
|
<SmallRenderer
|
||||||
|
key={event.start.toString() + event.event.uid}
|
||||||
|
event={event}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
if (eligibleEvents.length > 2) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="outline rounded--large p--2 flex flex--col gap--small">
|
||||||
|
<SectionLabel text="LATER" inverted />
|
||||||
|
|
||||||
|
<div className="flex flex--col gap--xsmall lg:gap--small">
|
||||||
|
{eligibleEvents.length ? eligibleEvents : null}
|
||||||
|
{eligibleEvents.length ? null : (
|
||||||
|
<div className="description">Niks anders gepland.</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<div className="layout">
|
||||||
|
<div className="flex flex--col gap--medium h--full">
|
||||||
|
<HeroPanel
|
||||||
|
event={data.current_event ?? data.next_event}
|
||||||
|
now={data.current_event !== undefined}
|
||||||
|
concurrent={data.concurrent_event_count}
|
||||||
|
/>
|
||||||
|
<div className="grid grid--cols-2 gap--medium grow">
|
||||||
|
<NextEvent
|
||||||
|
event={
|
||||||
|
data.current_event ? data.secondary_event : data.next_event
|
||||||
|
}
|
||||||
|
hasNow={!!data.current_event}
|
||||||
|
/>
|
||||||
|
<LaterEvents data={data} events={events} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<TitleBar
|
||||||
|
title={now.substring(0, now.length - 1) + 'x'}
|
||||||
|
instance={titlebarFlag}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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<ProcessedEvent[]> {
|
||||||
|
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<string> {
|
||||||
|
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 (
|
||||||
|
'<div class="view view--full">' +
|
||||||
|
renderToString(<Main data={data} events={events} />) +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
tsconfig.json
Normal file
30
tsconfig.json
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue