diff --git a/docs-site/docs/reference/environment-variables.md b/docs-site/docs/reference/environment-variables.md index da227d7..1c85aa3 100644 --- a/docs-site/docs/reference/environment-variables.md +++ b/docs-site/docs/reference/environment-variables.md @@ -65,7 +65,6 @@ Controls development vs production behavior in client/server code paths. - DOCX upload/conversion availability in upload UI - Default provider/model behavior for first-run TTS config - DeepInfra model picker restrictions when no API key is set - - Privacy modal wording (hosted-service vs local/dev wording) - Dev-only destructive document actions in settings ### NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT diff --git a/package.json b/package.json index 3c84549..6ae38d4 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "@aws-sdk/s3-request-presigner": "^3.987.0", "@headlessui/react": "^2.2.9", "@napi-rs/canvas": "^0.1.91", + "@types/archiver": "^7.0.0", "@vercel/analytics": "^1.6.1", + "archiver": "^7.0.1", "better-auth": "^1.4.18", "better-sqlite3": "^12.6.2", "cmpstr": "^3.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5b09ee..120010e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,9 +25,15 @@ importers: '@napi-rs/canvas': specifier: ^0.1.91 version: 0.1.91 + '@types/archiver': + specifier: ^7.0.0 + version: 7.0.0 '@vercel/analytics': specifier: ^1.6.1 version: 1.6.1(next@15.5.12(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + archiver: + specifier: ^7.0.1 + version: 7.0.1 better-auth: specifier: ^1.4.18 version: 1.4.18(@prisma/client@5.22.0)(better-sqlite3@12.6.2)(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.18.0))(next@15.5.12(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.18.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -905,6 +911,10 @@ packages: cpu: [x64] os: [win32] + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1069,6 +1079,10 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@playwright/test@1.58.2': resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} engines: {node: '>=18'} @@ -1377,6 +1391,9 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/archiver@7.0.0': + resolution: {integrity: sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==} + '@types/better-sqlite3@7.6.13': resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} @@ -1424,6 +1441,9 @@ packages: '@types/react@19.2.13': resolution: {integrity: sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==} + '@types/readdir-glob@1.1.5': + resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1620,6 +1640,10 @@ packages: resolution: {integrity: sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==} engines: {node: '>=14.6'} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1637,10 +1661,22 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -1648,6 +1684,14 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -1697,6 +1741,9 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + attr-accept@2.2.5: resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==} engines: {node: '>=4'} @@ -1713,12 +1760,28 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + b4a@1.7.5: + resolution: {integrity: sha512-iEsKNwDh1wiWTps1/hdkNdmBgDlDVZP5U57ZVOlt+dNFqpc/lpPouCIxZw+DYBgc4P9NDfIZMPNR4CHNhzwLIA==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bare-events@2.8.2: + resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1819,12 +1882,19 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1906,6 +1976,10 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + compromise@14.14.5: resolution: {integrity: sha512-9qWxpOWo4crzvbdxAYDTwO6z0WljXwi6mL7CqCjAXKn7QtFijmSj7fCyAqGWldCVT2zNboMvg4kNL06drMg2Vw==} engines: {node: '>=12.0.0'} @@ -1923,6 +1997,15 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2134,10 +2217,16 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + efrt@2.7.0: resolution: {integrity: sha512-/RInbCy1d4P6Zdfa+TMVsf/ufZVotat5hCw3QXmWtjU+3pFEOvOQ7ibo3aIxyCJw2leIeAMjmPj+1SLJiCpdrQ==} engines: {node: '>=12.0.0'} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -2346,6 +2435,17 @@ packages: event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -2359,6 +2459,9 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} @@ -2427,6 +2530,10 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -2480,6 +2587,11 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -2497,6 +2609,9 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + grad-school@0.0.5: resolution: {integrity: sha512-rXunEHF9M9EkMydTBux7+IryYXEZinRk6g8OBOGDBzo/qWJjhTxy86i5q7lQYpCLHN8Sqv1XX3OIOc7ka2gtvQ==} engines: {node: '>=8.0.0'} @@ -2641,6 +2756,10 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-generator-function@1.1.2: resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} @@ -2684,6 +2803,10 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -2721,6 +2844,9 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true @@ -2769,6 +2895,10 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -2806,6 +2936,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.2.6: resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} @@ -2978,6 +3111,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -2985,6 +3122,10 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -3116,6 +3257,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -3140,6 +3284,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-webpack@0.0.3: resolution: {integrity: sha512-AmeDxedoo5svf7aB3FYqSAKqMxys014lVKBzy1o/5vv9CtU7U4wgGWL1dA2o6MOzcD53ScN4Jmiq6VbtLz1vIQ==} @@ -3301,6 +3449,10 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -3402,6 +3554,13 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -3531,6 +3690,10 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -3562,6 +3725,17 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + streamx@2.23.0: + resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -3594,6 +3768,14 @@ packages: stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -3659,6 +3841,12 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -3809,6 +3997,14 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -3820,6 +4016,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -4732,6 +4932,15 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4848,6 +5057,9 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@pkgjs/parseargs@0.11.0': + optional: true + '@playwright/test@1.58.2': dependencies: playwright: 1.58.2 @@ -5280,6 +5492,10 @@ snapshots: tslib: 2.8.1 optional: true + '@types/archiver@7.0.0': + dependencies: + '@types/readdir-glob': 1.1.5 + '@types/better-sqlite3@7.6.13': dependencies: '@types/node': 20.19.33 @@ -5330,6 +5546,10 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/readdir-glob@1.1.5': + dependencies: + '@types/node': 20.19.33 + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -5495,6 +5715,10 @@ snapshots: '@xmldom/xmldom@0.9.8': {} + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -5514,10 +5738,16 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansi-styles@6.2.3: {} + any-promise@1.3.0: {} anymatch@3.1.3: @@ -5525,6 +5755,29 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + archiver-utils@5.0.2: + dependencies: + glob: 10.5.0 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.17.23 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 + zip-stream: 6.0.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + arg@5.0.2: {} argparse@2.0.1: {} @@ -5602,6 +5855,8 @@ snapshots: async-function@1.0.0: {} + async@3.2.6: {} + attr-accept@2.2.5: {} available-typed-arrays@1.0.7: @@ -5612,10 +5867,14 @@ snapshots: axobject-query@4.1.0: {} + b4a@1.7.5: {} + bail@2.0.2: {} balanced-match@1.0.2: {} + bare-events@2.8.2: {} + base64-js@1.5.1: {} better-auth@1.4.18(@prisma/client@5.22.0)(better-sqlite3@12.6.2)(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.18.0))(next@15.5.12(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.18.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): @@ -5683,6 +5942,8 @@ snapshots: dependencies: fill-range: 7.1.1 + buffer-crc32@1.0.0: {} + buffer-from@1.1.2: {} buffer@5.7.1: @@ -5690,6 +5951,11 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -5766,6 +6032,14 @@ snapshots: commander@4.1.1: {} + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + compromise@14.14.5: dependencies: efrt: 2.7.0 @@ -5785,6 +6059,13 @@ snapshots: core-util-is@1.0.3: {} + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.7.0 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -5910,8 +6191,12 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + eastasianwidth@0.2.0: {} + efrt@2.7.0: {} + emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} empty-module@0.0.2: {} @@ -6328,6 +6613,16 @@ snapshots: d: 1.0.2 es5-ext: 0.10.64 + event-target-shim@5.0.1: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - bare-abort-controller + + events@3.3.0: {} + expand-template@2.0.3: {} ext@1.7.0: @@ -6338,6 +6633,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.1: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6413,6 +6710,11 @@ snapshots: dependencies: is-callable: 1.2.7 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + fs-constants@1.0.0: {} fsevents@2.3.2: @@ -6474,6 +6776,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + globals@14.0.0: {} globalthis@1.0.4: @@ -6487,6 +6798,8 @@ snapshots: gopd@1.2.0: {} + graceful-fs@4.2.11: {} + grad-school@0.0.5: {} has-bigints@1.1.0: {} @@ -6644,6 +6957,8 @@ snapshots: dependencies: call-bound: 1.0.4 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.2: dependencies: call-bound: 1.0.4 @@ -6684,6 +6999,8 @@ snapshots: dependencies: call-bound: 1.0.4 + is-stream@2.0.1: {} + is-string@1.1.1: dependencies: call-bound: 1.0.4 @@ -6725,6 +7042,12 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jiti@1.21.7: {} jose@6.1.3: {} @@ -6771,6 +7094,10 @@ snapshots: dependencies: language-subtag-registry: 0.3.23 + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -6806,6 +7133,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@10.4.3: {} + lru-cache@11.2.6: {} make-cancellable-promise@1.3.2: {} @@ -7179,12 +7508,18 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 minimist@1.2.8: {} + minipass@7.1.2: {} + mkdirp-classic@0.5.3: {} ms@2.1.3: {} @@ -7315,6 +7650,8 @@ snapshots: dependencies: p-limit: 3.1.0 + package-json-from-dist@1.0.1: {} + pako@1.0.11: {} parent-module@1.0.1: @@ -7339,6 +7676,11 @@ snapshots: path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-webpack@0.0.3: {} path2d@0.2.2: @@ -7481,6 +7823,8 @@ snapshots: process-nextick-args@2.0.1: {} + process@0.11.10: {} + progress@2.0.3: {} prop-types@15.8.1: @@ -7610,6 +7954,18 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -7817,6 +8173,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + signal-exit@4.1.0: {} + simple-concat@1.0.1: {} simple-get@4.0.1: @@ -7845,6 +8203,27 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + streamx@2.23.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + string.prototype.includes@2.0.1: dependencies: call-bind: 1.0.8 @@ -7908,6 +8287,14 @@ snapshots: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + strip-bom@3.0.0: {} strip-json-comments@2.0.1: {} @@ -7992,6 +8379,21 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + tar-stream@3.1.7: + dependencies: + b4a: 1.7.5 + fast-fifo: 1.3.2 + streamx: 2.23.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + text-decoder@1.2.7: + dependencies: + b4a: 1.7.5 + transitivePeerDependencies: + - react-native-b4a + thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -8216,12 +8618,30 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + wrappy@1.0.2: {} xtend@4.0.2: {} yocto-queue@0.1.0: {} + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.7.0 + zod@4.3.6: {} zwitch@2.0.4: {} diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx index ebe4664..e84a231 100644 --- a/src/app/(public)/page.tsx +++ b/src/app/(public)/page.tsx @@ -347,7 +347,10 @@ export default async function LandingPage({ {/* ── HERO ───────────────────── */}
- Document Reader + TTS + {process.env.RICHARDRDEV_PRODUCTION === 'true' + ? "Official OpenReader Instance" + : "Open Source Document Reader" + }

Your documents, read aloud @@ -360,7 +363,7 @@ export default async function LandingPage({
Open App - + Sign In Sign Up diff --git a/src/app/(public)/privacy/page.tsx b/src/app/(public)/privacy/page.tsx index a57b5d4..96575b6 100644 --- a/src/app/(public)/privacy/page.tsx +++ b/src/app/(public)/privacy/page.tsx @@ -2,8 +2,6 @@ import type { Metadata } from 'next'; import Link from 'next/link'; import { headers } from 'next/headers'; -import { isAuthEnabled } from '@/lib/server/auth-config'; - export const metadata: Metadata = { title: 'Privacy & Data Usage | OpenReader WebUI', description: @@ -18,7 +16,8 @@ export const metadata: Metadata = { }; export default async function PrivacyPage() { - const authEnabled = isAuthEnabled(); + const effectiveDate = 'February 17, 2026'; + const hdrs = await headers(); const host = hdrs.get('host') ?? 'this server'; const proto = hdrs.get('x-forwarded-proto') ?? 'https'; @@ -139,48 +138,152 @@ export default async function PrivacyPage() {

Privacy & Data Usage

- How OpenReader WebUI handles your data when hosted at this instance. + Effective Date: {effectiveDate}

This OpenReader instance is hosted at {origin}. - The operator of this service can access data that reaches the service. + The operator of this service is responsible for handling your information.
-
-
Stored in your browser (IndexedDB)
-
    -
  • Document and preview cache
  • -
  • Settings + privacy acceptance
  • -
  • Reading progress (local fallback)
  • -
+
+ OpenReader does not sell your personal information. We do not sell data to data brokers or third parties. + We use data solely to provide and improve the reading experience.
-
-
Sent to this service
-
    -
  • Uploaded files + metadata (PDF/EPUB/HTML; DOCX converted server-side)
  • -
  • TTS text + settings (optional custom API key/base URL)
  • -
  • Request metadata (IP/user agent) and optional alignment audio/text
  • -
+
+
+

+ + 1. Information We Collect (CCPA Categories) +

+

+ We collect information that identifies, relates to, describes, references, or is reasonably capable of being associated with you ("Personal Information"). +

+
+
Categories Collected
+
    +
  • + Identifiers: Email address, IP address, unique personal identifier (session token), and account name. +
    Source: Directly from you. Purpose: Authentication, security, providing service.
    +
  • +
  • + Customer Records: Uploaded documents (PDF, EPUB), reading progress, bookmarks, and preferences. +
    Source: Directly from you. Purpose: Providing core reading functionality.
    +
  • +
  • + Internet Activity: Browsing history within the app, interaction with features (Analytics). +
    Source: Automatic collection. Purpose: Debugging, performance optimization.
    +
  • +
+
+
+ +
+

+ + 2. How We Use Your Information +

+
    +
  • To provide, support, and personalize the OpenReader application.
  • +
  • To process your uploaded documents for display and text-to-speech conversion.
  • +
  • To maintain the safety, security, and integrity of our service.
  • +
  • To debug and repair errors that impair existing intended functionality.
  • +
+
+ +
+

+ + 3. Sharing & Selling +

+

+ We do not sell your personal information. +

+

+ We may "share" (as defined by CPRA for cross-context behavioral advertising) anonymous usage data with analytics providers solely to improve our app. You can opt-out of this sharing via the Cookie Banner, and Global Privacy Control (GPC) signals are automatically honored. +

+
+
Service Providers (Sub-processors)
+ {process.env.RICHARDRDEV_PRODUCTION === 'true' ? ( +
    +
  • Vercel: Hosting, Edge Functions & Analytics
  • +
  • Neon (PostgreSQL): Database Storage
  • +
  • Railway (S3): Encrypted Object Storage (Documents)
  • +
  • DeepInfra: Text-to-Speech Processing (User-Initiated)
  • +
+ ) : ( +
    +
  • Hosting Provider: Application Hosting & Logs
  • +
  • Database Service: Relational Database Storage
  • +
  • Object Storage: Encrypted File Storage (Documents)
  • +
  • TTS Provider: Text-to-Speech Processing (Optional)
  • +
+ )} +
+
+ +
+

+ + 4. Your Rights (CCPA/CPRA) +

+

+ You have the following rights regarding your personal information: +

+
+
    +
  • Right to Know: You may request details about the categories and specific pieces of personal information we have collected.
  • +
  • Right to Delete: You may request deletion of your personal information (via "Delete Account" in Settings).
  • +
  • Right to Correct: You may update your account information in Settings.
  • +
  • Right to Opt-Out: We do not sell data. You may opt-out of analytics "sharing" via our Cookie Banner.
  • +
  • Right to Non-Discrimination: We will not discriminate against you for exercising your privacy rights.
  • +
+
+
+

+ How to Exercise Your Rights: +

+
    +
  • Export Data: Use the "Export My Data" button in Settings to download your account metadata plus object-storage-backed document and audiobook files.
  • +
  • Delete Data: Use the "Delete Account" button in Settings.
  • +
+
+
+ +
+

+ + 5. Data Retention +

+

+ We retain your account data and uploaded files only for as long as you maintain an account. + Uploaded documents are stored encrypted at rest (AES-256). + Upon account deletion, all data is permanently removed from our active databases and storage buckets immediately. +

+
+ +
+

+ + 6. Contact Us +

+

+ If you have questions or concerns about this Privacy Policy, please contact the instance administrator via the repository: +

+ + OpenReader WebUI Issues + +
-
-
Stored on this service
-
    -
  • Uploaded docs, metadata, and preview images
  • -
  • Generated audiobooks and temporary TTS cache
  • - {authEnabled ? ( -
  • Account/session data, synced preferences/progress, and rate-limit counters
  • - ) : ( -
  • Auth disabled — no account or session tables
  • - )} -
-
- -

- This site uses Vercel Analytics to collect anonymous usage data. - For maximum privacy, self-host OpenReader using the{' '} +

+ For maximum privacy, you can self-host OpenReader using the{' '} - + Back to home

diff --git a/src/app/api/user/export/route.ts b/src/app/api/user/export/route.ts new file mode 100644 index 0000000..bf1eac4 --- /dev/null +++ b/src/app/api/user/export/route.ts @@ -0,0 +1,149 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { PassThrough, Readable } from 'stream'; +import { auth } from '@/lib/server/auth'; +import { db } from '@/db'; +import { documents, audiobooks, audiobookChapters, userPreferences, userDocumentProgress, userTtsChars } from '@/db/schema'; +import { and, desc, eq, inArray } from 'drizzle-orm'; +import archiver from 'archiver'; +import { appendUserExportArchive } from '@/lib/server/user-export'; +import { getDocumentBlobStream } from '@/lib/server/documents-blobstore'; +import { getAudiobookObjectStream, listAudiobookObjects } from '@/lib/server/audiobooks-blobstore'; +import { isS3Configured } from '@/lib/server/s3'; +import { getOpenReaderTestNamespace } from '@/lib/server/test-namespace'; + +export const dynamic = 'force-dynamic'; + +export async function GET(req: NextRequest) { + if (!isS3Configured()) { + return NextResponse.json( + { error: 'Export storage is not configured. Set S3_* environment variables.' }, + { status: 503 }, + ); + } + + if (!auth) { + return NextResponse.json({ error: 'Auth not initialized' }, { status: 500 }); + } + + const session = await auth.api.getSession({ + headers: req.headers, + }); + + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const userId = session.user.id; + const testNamespace = getOpenReaderTestNamespace(req.headers); + + const [ + prefs, + progress, + ttsUsage, + userDocs, + userAudiobooks, + ] = await Promise.all([ + db.select().from(userPreferences).where(eq(userPreferences.userId, userId)).limit(1), + db + .select() + .from(userDocumentProgress) + .where(eq(userDocumentProgress.userId, userId)) + .orderBy(desc(userDocumentProgress.updatedAt)), + db + .select() + .from(userTtsChars) + .where(eq(userTtsChars.userId, userId)) + .orderBy(desc(userTtsChars.date)), + db + .select() + .from(documents) + .where(eq(documents.userId, userId)) + .orderBy(desc(documents.lastModified)), + db + .select() + .from(audiobooks) + .where(eq(audiobooks.userId, userId)) + .orderBy(desc(audiobooks.createdAt)), + ]); + + const archive = archiver('zip', { + zlib: { level: 0 }, + forceZip64: true, + }); + + const output = new PassThrough(); + archive.pipe(output); + + archive.on('warning', (warning) => { + if ((warning as NodeJS.ErrnoException).code !== 'ENOENT') { + console.error('User export warning:', warning); + } + }); + + archive.on('error', (error) => { + output.destroy(error); + }); + + const bookIds = userAudiobooks.map((book: typeof audiobooks.$inferSelect) => book.id); + const allChapters = bookIds.length > 0 + ? await db + .select() + .from(audiobookChapters) + .where(and(eq(audiobookChapters.userId, userId), inArray(audiobookChapters.bookId, bookIds))) + : []; + + const onAbort = () => { + archive.abort(); + output.destroy(new Error('Export request aborted')); + }; + req.signal.addEventListener('abort', onAbort, { once: true }); + + (async () => { + try { + const exportedAtIso = new Date().toISOString(); + const profileData = { + user: session.user, + session: session.session, + exportedAt: exportedAtIso, + }; + + await appendUserExportArchive({ + archive, + userId, + exportedAtIso, + profileData, + preferences: prefs[0] ?? null, + readingHistory: progress, + ttsUsage, + documents: userDocs, + audiobooks: userAudiobooks, + audiobookChapters: allChapters, + getDocumentBlobStream: async (documentId: string) => getDocumentBlobStream(documentId, testNamespace), + listAudiobookObjects: async (bookId: string, ownerId: string) => listAudiobookObjects(bookId, ownerId, testNamespace), + getAudiobookObjectStream: async (bookId: string, ownerId: string, fileName: string) => + getAudiobookObjectStream(bookId, ownerId, fileName, testNamespace), + }); + + if (!req.signal.aborted) { + await archive.finalize(); + } + } catch (error) { + console.error('Export generation failed:', error); + archive.abort(); + output.destroy(error instanceof Error ? error : new Error('Failed to generate export archive')); + } finally { + req.signal.removeEventListener('abort', onAbort); + if (req.signal.aborted) { + output.destroy(new Error('Export request aborted')); + } + } + })(); + + return new NextResponse(Readable.toWeb(output) as ReadableStream, { + headers: { + 'Content-Type': 'application/zip', + 'Content-Disposition': `attachment; filename="openreader-data-${userId.slice(0, 8)}.zip"`, + 'Cache-Control': 'no-store', + }, + }); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b190336..77281a1 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,8 +1,9 @@ import "./globals.css"; import { ReactNode } from "react"; import { Metadata } from "next"; -import { Analytics } from "@vercel/analytics/next"; import { Figtree } from "next/font/google"; +import { ConsentAwareAnalytics } from "@/components/ConsentAwareAnalytics"; +import { CookieConsentBanner } from "@/components/CookieConsentBanner"; const figtree = Figtree({ subsets: ["latin"], @@ -47,7 +48,8 @@ export default function RootLayout({ children }: { children: ReactNode }) { {children} - + + ); diff --git a/src/components/ConsentAwareAnalytics.tsx b/src/components/ConsentAwareAnalytics.tsx new file mode 100644 index 0000000..d450a09 --- /dev/null +++ b/src/components/ConsentAwareAnalytics.tsx @@ -0,0 +1,33 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { Analytics } from '@vercel/analytics/next'; +import { CONSENT_CHANGED_EVENT, disableAnalytics, getConsentState } from '@/lib/analytics'; + +export function ConsentAwareAnalytics() { + const [enabled, setEnabled] = useState(false); + const [ready, setReady] = useState(false); + + useEffect(() => { + const sync = () => { + const allowAnalytics = getConsentState() !== 'declined'; + setEnabled(allowAnalytics); + if (!allowAnalytics) { + disableAnalytics(); + } + setReady(true); + }; + + sync(); + window.addEventListener(CONSENT_CHANGED_EVENT, sync); + window.addEventListener('storage', sync); + + return () => { + window.removeEventListener(CONSENT_CHANGED_EVENT, sync); + window.removeEventListener('storage', sync); + }; + }, []); + + if (!ready || !enabled) return null; + return ; +} diff --git a/src/components/CookieConsentBanner.tsx b/src/components/CookieConsentBanner.tsx new file mode 100644 index 0000000..2f88eb5 --- /dev/null +++ b/src/components/CookieConsentBanner.tsx @@ -0,0 +1,73 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { getConsentState, setConsentState } from '@/lib/analytics'; +import { Transition } from '@headlessui/react'; + +export function CookieConsentBanner() { + const [show, setShow] = useState(false); + + useEffect(() => { + // Check consent on mount + const consent = getConsentState(); + if (consent === 'undecided') { + // Small delay to prevent layout thrashing on load + const timer = setTimeout(() => setShow(true), 1000); + return () => clearTimeout(timer); + } + }, []); + + const handleAccept = () => { + setConsentState('accepted'); + setShow(false); + }; + + const handleDecline = () => { + setConsentState('declined'); + setShow(false); + }; + + if (!show) return null; + + return ( + +
+
+

+ 🍪 We use cookies +

+

+ We use strictly necessary cookies for authentication and optional cookies for anonymous analytics + to improve the app. See our Privacy Policy for details. +

+
+ +
+ + +
+
+
+ ); +} diff --git a/src/components/PrivacyModal.tsx b/src/components/PrivacyModal.tsx index 0a0f7d3..68afbd4 100644 --- a/src/components/PrivacyModal.tsx +++ b/src/components/PrivacyModal.tsx @@ -11,120 +11,37 @@ import { } from '@headlessui/react'; import { updateAppConfig, getAppConfig } from '@/lib/dexie'; -const isDev = process.env.NEXT_PUBLIC_NODE_ENV !== 'production' || process.env.NODE_ENV == null; - interface PrivacyModalProps { onAccept?: () => void; authEnabled?: boolean; } -function PrivacyModalBody({ - origin, - authEnabled, -}: { - origin: string; - authEnabled: boolean; -}) { - if (!isDev) { - return ( -
-
-
Service operator visibility
-
- This OpenReader instance is hosted at {origin || 'this server'}. The operator - of this service can access data that reaches the service. -
-
- -
-
Stored in your browser (IndexedDB)
-
    -
  • Document and preview cache
  • -
  • Settings + privacy acceptance
  • -
  • Reading progress (local fallback)
  • -
-
- -
-
Sent to this service
-
    -
  • Uploaded files + metadata (PDF/EPUB/HTML; DOCX converted server-side)
  • -
  • TTS text + settings (optional custom API key/base URL)
  • -
  • Request metadata (IP/user agent) and optional alignment audio/text
  • -
-
- -
-
Stored on this service
-
    -
  • Uploaded docs, metadata, and preview images
  • -
  • Generated audiobooks and temporary TTS cache
  • - {authEnabled ? ( -
  • Account/session data, synced preferences/progress, and rate-limit counters
  • - ) : ( -
  • Auth disabled: no account/session tables
  • - )} -
-
- -
- This site uses Vercel Analytics to collect anonymous usage data. For maximum privacy, use self-hosted mode. -
-
- ); - } - +function PrivacyModalBody({ origin }: { origin: string }) { return (
-
Server owner visibility
-
- This OpenReader instance is hosted at {origin || 'this server'}. The operator - of this server can access data that reaches the server. +
Service Operator
+
+ This instance is hosted at {origin || 'this server'}.
-
-
Stored in your browser (IndexedDB)
-
    -
  • Document and preview cache
  • -
  • Settings + privacy acceptance
  • -
  • Reading progress (local fallback)
  • -
-
+

+ We value your privacy. This application uses strictly necessary cookies for authentication + and anonymous analytics to improve performance. Your documents are stored securely and encrypted at rest. +

-
-
Sent to this server
-
    -
  • Uploaded files + metadata (PDF/EPUB/HTML; DOCX converted server-side)
  • -
  • TTS text + settings (optional custom API key/base URL)
  • -
  • Request metadata (IP/user agent; device ID if rate limiting is enabled) and optional alignment audio/text
  • -
-
- -
-
Stored on this server
-
    -
  • Uploaded docs, metadata, and preview images
  • -
  • Generated audiobooks and temporary TTS cache
  • - {authEnabled ? ( -
  • Account/session data, synced preferences/progress, and rate-limit counters
  • - ) : ( -
  • Auth disabled: no account/session tables
  • - )} -
-
- -
- Tip: If you are behind a reverse proxy, the proxy operator may also have access to request logs. -
+

+ For full details on data collection, processing, and your rights, please review our complete Privacy Policy. +

); } -export function PrivacyModal({ onAccept, authEnabled = false }: PrivacyModalProps) { +export function PrivacyModal({ onAccept }: PrivacyModalProps) { const [isOpen, setIsOpen] = useState(false); const [origin, setOrigin] = useState(''); + const [agreed, setAgreed] = useState(false); const checkPrivacyAccepted = useCallback(async () => { const config = await getAppConfig(); @@ -187,19 +104,43 @@ export function PrivacyModal({ onAccept, authEnabled = false }: PrivacyModalProp Privacy & Data Usage - + -
- + @@ -221,7 +162,7 @@ export function showPrivacyModal(options?: { authEnabled?: boolean }): void { document.body.appendChild(container); const origin = typeof window !== 'undefined' ? window.location.origin : ''; - const authEnabled = Boolean(options?.authEnabled); + void options; // Import React and render the popup import('react-dom/client').then(({ createRoot }) => { @@ -277,7 +218,7 @@ export function showPrivacyModal(options?: { authEnabled?: boolean }): void { Privacy & Data Usage - +
-
+
+ {session?.user && ( + + )} + {session?.user && !session.user.isAnonymous ? ( <>