From b4da2535835f6beb3df6da1fefa32dfa8c807b60 Mon Sep 17 00:00:00 2001 From: Xevion Date: Thu, 7 Nov 2024 11:02:50 -0600 Subject: [PATCH 001/100] add pwdlib[argon2] packages for hashing --- backend/poetry.lock | 167 ++++++++++++++++++++++++++++++++++++++++- backend/pyproject.toml | 1 + 2 files changed, 167 insertions(+), 1 deletion(-) diff --git a/backend/poetry.lock b/backend/poetry.lock index ab5400b..79295c1 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -70,6 +70,63 @@ tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] +[[package]] +name = "argon2-cffi" +version = "23.1.0" +description = "Argon2 for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, + {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, +] + +[package.dependencies] +argon2-cffi-bindings = "*" + +[package.extras] +dev = ["argon2-cffi[tests,typing]", "tox (>4)"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] +tests = ["hypothesis", "pytest"] +typing = ["mypy"] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" +optional = false +python-versions = ">=3.6" +files = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, +] + +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["cogapp", "pre-commit", "pytest", "wheel"] +tests = ["pytest"] + [[package]] name = "asgi-correlation-id" version = "4.3.4" @@ -140,6 +197,85 @@ files = [ {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "charset-normalizer" version = "3.4.0" @@ -864,6 +1000,35 @@ files = [ {file = "psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11"}, ] +[[package]] +name = "pwdlib" +version = "0.2.1" +description = "Modern password hashing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pwdlib-0.2.1-py3-none-any.whl", hash = "sha256:1823dc6f22eae472b540e889ecf57fd424051d6a4023ec0bcf7f0de2d9d7ef8c"}, + {file = "pwdlib-0.2.1.tar.gz", hash = "sha256:9a1d8a8fa09a2f7ebf208265e55d7d008103cbdc82b9e4902ffdd1ade91add5e"}, +] + +[package.dependencies] +argon2-cffi = {version = ">=23.1.0,<24", optional = true, markers = "extra == \"argon2\""} + +[package.extras] +argon2 = ["argon2-cffi (>=23.1.0,<24)"] +bcrypt = ["bcrypt (>=4.1.2,<5)"] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + [[package]] name = "pydantic" version = "2.9.2" @@ -1278,4 +1443,4 @@ h11 = ">=0.9.0,<1" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "6cab1d930ad04560919f18c779fe4f9f2d28aba84d4385ed01aff1c876e453b6" +content-hash = "b5e06928b75df2a2428032f6e37b43e44d42b184654770686471125770abe3da" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 7e2794d..1725321 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -28,6 +28,7 @@ uvicorn = "^0.32.0" asgi-correlation-id = "^4.3.4" orjson = "^3.10.10" hypercorn = "^0.17.3" +pwdlib = {extras = ["argon2"], version = "^0.2.1"} [tool.poetry.group.dev.dependencies] From 04ed915f28e3fe7abb0e18514e777884232b3c4d Mon Sep 17 00:00:00 2001 From: Xevion Date: Thu, 7 Nov 2024 11:03:06 -0600 Subject: [PATCH 002/100] backend run.sh defaults to pretty logs on debug level --- backend/run.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/run.sh b/backend/run.sh index ac6ddc8..9423046 100755 --- a/backend/run.sh +++ b/backend/run.sh @@ -8,6 +8,8 @@ fi # Default to development mode if not defined export ENVIRONMENT=${ENVIRONMENT:-development} +export LOG_JSON_FORMAT=${LOG_JSON_FORMAT:-false} +export LOG_LEVEL=${LOG_LEVEL:-debug} COMMAND='poetry run python3 -m linkpulse' # Check if Railway CLI is available From 361fc13741ead00c39d3313c34d1a438dfb571e0 Mon Sep 17 00:00:00 2001 From: Xevion Date: Thu, 7 Nov 2024 11:03:21 -0600 Subject: [PATCH 003/100] Remove IPAddress model, add User model --- .../004_create_user_remove_ipaddress.py | 66 +++++++++++++++++++ backend/linkpulse/models.py | 15 +++-- 2 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 backend/linkpulse/migrations/004_create_user_remove_ipaddress.py diff --git a/backend/linkpulse/migrations/004_create_user_remove_ipaddress.py b/backend/linkpulse/migrations/004_create_user_remove_ipaddress.py new file mode 100644 index 0000000..9322940 --- /dev/null +++ b/backend/linkpulse/migrations/004_create_user_remove_ipaddress.py @@ -0,0 +1,66 @@ +"""Peewee migrations -- 004_create_user_remove_ipaddress.py. + +Some examples (model - class or model name):: + + > Model = migrator.orm['table_name'] # Return model in current state by name + > Model = migrator.ModelClass # Return model in current state by name + + > migrator.sql(sql) # Run custom SQL + > migrator.run(func, *args, **kwargs) # Run python function with the given args + > migrator.create_model(Model) # Create a model (could be used as decorator) + > migrator.remove_model(model, cascade=True) # Remove a model + > migrator.add_fields(model, **fields) # Add fields to a model + > migrator.change_fields(model, **fields) # Change fields + > migrator.remove_fields(model, *field_names, cascade=True) + > migrator.rename_field(model, old_field_name, new_field_name) + > migrator.rename_table(model, new_table_name) + > migrator.add_index(model, *col_names, unique=False) + > migrator.add_not_null(model, *field_names) + > migrator.add_default(model, field_name, default) + > migrator.add_constraint(model, name, sql) + > migrator.drop_index(model, *col_names) + > migrator.drop_not_null(model, *field_names) + > migrator.drop_constraints(model, *constraints) + +""" + +from contextlib import suppress + +import peewee as pw +from peewee_migrate import Migrator + + +with suppress(ImportError): + import playhouse.postgres_ext as pw_pext + + +def migrate(migrator: Migrator, database: pw.Database, *, fake=False): + """Write your migrations here.""" + + @migrator.create_model + class User(pw.Model): + id = pw.AutoField() + email = pw.CharField(max_length=45, unique=True) + password_hash = pw.CharField(max_length=96) + created_at = pw.DateTimeField() + updated_at = pw.DateTimeField() + + class Meta: + table_name = "user" + + migrator.remove_model('ipaddress') + + +def rollback(migrator: Migrator, database: pw.Database, *, fake=False): + """Write your rollback migrations here.""" + + @migrator.create_model + class IPAddress(pw.Model): + ip = pw.CharField(max_length=255, primary_key=True) + last_seen = pw.DateTimeField() + count = pw.IntegerField(default=0) + + class Meta: + table_name = "ipaddress" + + migrator.remove_model('user') diff --git a/backend/linkpulse/models.py b/backend/linkpulse/models.py index 2ae1be2..ec7fc70 100644 --- a/backend/linkpulse/models.py +++ b/backend/linkpulse/models.py @@ -3,10 +3,11 @@ This module defines the database models for the LinkPulse backend. It also provides a base model with database connection details. """ +from datetime import datetime from os import getenv import structlog -from peewee import CharField, DateTimeField, IntegerField, Model +from peewee import CharField, DateTimeField, IntegerField, AutoField, Model from playhouse.db_url import connect logger = structlog.get_logger() @@ -26,7 +27,11 @@ class BaseModel(Model): database = connect(url=_get_database_url()) -class IPAddress(BaseModel): - ip = CharField(primary_key=True) - last_seen = DateTimeField() # timezone naive - count = IntegerField(default=0) +class User(BaseModel): + id = AutoField(primary_key=True) + # arbitrary max length, but statistically reasonable and limits UI concerns/abuse cases + email = CharField(unique=True, max_length=45) + # full hash with encoded salt/parameters, argon2 but assume nothing + password_hash = CharField(max_length=96) + created_at = DateTimeField(default=datetime.now) + updated_at = DateTimeField(default=datetime.now) From bc1a6f927f9af84882707fe8bac1d5ef32ff8061 Mon Sep 17 00:00:00 2001 From: Xevion Date: Thu, 7 Nov 2024 11:30:34 -0600 Subject: [PATCH 004/100] Add utc_now for deprecated datetime.datetime.utcnow --- backend/linkpulse/utilities.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/linkpulse/utilities.py b/backend/linkpulse/utilities.py index 3aecf5f..4cb2a6f 100644 --- a/backend/linkpulse/utilities.py +++ b/backend/linkpulse/utilities.py @@ -3,8 +3,10 @@ This module provides utility functions for database connection, string manipulat """ import os +from datetime import datetime from typing import Optional +import pytz from fastapi import Request from peewee import PostgresqlDatabase @@ -12,6 +14,13 @@ from peewee import PostgresqlDatabase is_development = os.getenv("ENVIRONMENT") == "development" +def utc_now() -> datetime: + """ + A utility function to replace the deprecated datetime.datetime.utcnow() function. + """ + return datetime.now(pytz.utc) + + def get_db() -> PostgresqlDatabase: """ Acquires the database connector from the BaseModel class. From 95b05e077ae7963499dc9831aaf5e5240cb69d92 Mon Sep 17 00:00:00 2001 From: Xevion Date: Thu, 7 Nov 2024 11:31:03 -0600 Subject: [PATCH 005/100] delete all old IPAddress related model code --- backend/linkpulse/app.py | 122 ++------------------------------------- 1 file changed, 6 insertions(+), 116 deletions(-) diff --git a/backend/linkpulse/app.py b/backend/linkpulse/app.py index b14601c..533f831 100644 --- a/backend/linkpulse/app.py +++ b/backend/linkpulse/app.py @@ -1,120 +1,54 @@ -import random -from collections import defaultdict from contextlib import asynccontextmanager -from dataclasses import dataclass, field -from datetime import datetime, timezone from typing import AsyncIterator -import human_readable -import pytz import structlog from apscheduler.schedulers.background import BackgroundScheduler # type: ignore from apscheduler.triggers.interval import IntervalTrigger # type: ignore from asgi_correlation_id import CorrelationIdMiddleware from dotenv import load_dotenv -from fastapi import FastAPI, Request, Response, status +from fastapi import FastAPI from fastapi.responses import ORJSONResponse from fastapi_cache import FastAPICache from fastapi_cache.backends.inmemory import InMemoryBackend from fastapi_cache.decorator import cache from linkpulse.logging import setup_logging from linkpulse.middleware import LoggingMiddleware -from linkpulse.utilities import get_db, get_ip, hide_ip, is_development -from psycopg2.extras import execute_values +from linkpulse.utilities import get_db, is_development load_dotenv(dotenv_path=".env") -from linkpulse import models, responses # type: ignore +from linkpulse import models # type: ignore db = get_db() -def flush_ips(): - if len(app.state.buffered_updates) == 0: - logger.debug("No IPs to flush to Database") - return - - try: - with db.atomic(): - sql = """ - WITH updates (ip, last_seen, increment) AS (VALUES %s) - INSERT INTO ipaddress (ip, last_seen, count) - SELECT ip, last_seen, increment - FROM updates - ON CONFLICT (ip) - DO UPDATE - SET count = ipaddress.count + EXCLUDED.count, last_seen = EXCLUDED.last_seen; - """ - rows = [ - (ip, data.last_seen, data.count) - for ip, data in app.state.buffered_updates.items() - ] - - cur = db.cursor() - execute_values(cur, sql, rows) - except Exception as e: - logger.error("Failed to flush IPs to Database", error=e) - - i = len(app.state.buffered_updates) - logger.debug("IPs written to database", count=i) - - # Finish up - app.state.buffered_updates.clear() - - scheduler = BackgroundScheduler() -scheduler.add_job(flush_ips, IntervalTrigger(seconds=5)) @asynccontextmanager async def lifespan(_: FastAPI) -> AsyncIterator[None]: - # Originally, this was used to generate a pool of random IP addresses so we could demo a changing list. - # Now, this isn't necessary, but I just wanna test it for now. It'll be removed pretty soon. - random.seed(42) # 42 is the answer to everything - app.state.ip_pool = [ - ".".join(str(random.randint(0, 255)) for _ in range(4)) for _ in range(50) - ] - # Connect to database, ensure specific tables exist db.connect() - db.create_tables([models.IPAddress]) - - # Delete all randomly generated IP addresses - with db.atomic(): - logger.info( - "Deleting Randomized IP Addresses", ip_pool_count=len(app.state.ip_pool) - ) - query = models.IPAddress.delete().where( - models.IPAddress.ip << app.state.ip_pool - ) - row_count = query.execute() - logger.info("Randomized IP Addresses deleted", row_count=row_count) + db.create_tables([models.User, models.Session]) FastAPICache.init( backend=InMemoryBackend(), prefix="fastapi-cache", cache_status_header="X-Cache" ) - app.state.buffered_updates = defaultdict(IPCounter) - scheduler.start() yield scheduler.shutdown() - flush_ips() if not db.is_closed(): db.close() -@dataclass -class IPCounter: - # Note: This is not the true 'seen' count, but the count of how many times the IP has been seen since the last flush. - count: int = 0 - last_seen: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) - +from routers import authentication app = FastAPI(lifespan=lifespan, default_response_class=ORJSONResponse) +app.include_router(authentication.router) setup_logging() @@ -155,47 +89,3 @@ async def get_migration(): ) name, migrated_at = cursor.fetchone() return {"name": name, "migrated_at": migrated_at} - - -@app.get("/api/ips") -async def get_ips(request: Request, response: Response): - """ - Returns a list of partially redacted IP addresses, as well as submitting the user's IP address to the database (buffered). - """ - now = datetime.now(timezone.utc) - - # Get the user's IP address - user_ip = get_ip(request) - - # If the IP address is not found, return an error - if user_ip is None: - logger.warning("unable to acquire user IP address") - response.status_code = status.HTTP_403_FORBIDDEN - return {"error": "Unable to handle request."} - - # Update the buffered updates - app.state.buffered_updates[user_ip].count += 1 - app.state.buffered_updates[user_ip].last_seen = now - - # Query the latest IPs - latest_ips = ( - models.IPAddress.select( - models.IPAddress.ip, models.IPAddress.last_seen, models.IPAddress.count - ) - .order_by(models.IPAddress.last_seen.desc()) - .limit(10) - ) - - return { - "ips": [ - responses.SeenIP( - ip=hide_ip(ip.ip) if ip.ip != user_ip else ip.ip, - last_seen=human_readable.date_time( - value=pytz.utc.localize(ip.last_seen), - when=datetime.now(timezone.utc), - ), - count=ip.count, - ) - for ip in latest_ips - ] - } From d612625de8aa7c41c9917719b76b22d3ef823d15 Mon Sep 17 00:00:00 2001 From: Xevion Date: Thu, 7 Nov 2024 11:32:24 -0600 Subject: [PATCH 006/100] Add session model, use utc_now utility --- .vscode/settings.json | 1 + backend/linkpulse/models.py | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 987f3c4..1774037 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ "apscheduler", + "backref", "bpython", "Callsite", "excepthook", diff --git a/backend/linkpulse/models.py b/backend/linkpulse/models.py index ec7fc70..26365e4 100644 --- a/backend/linkpulse/models.py +++ b/backend/linkpulse/models.py @@ -7,9 +7,11 @@ from datetime import datetime from os import getenv import structlog -from peewee import CharField, DateTimeField, IntegerField, AutoField, Model +from peewee import AutoField, CharField, DateTimeField, ForeignKeyField, Model from playhouse.db_url import connect +from linkpulse.utilities import utc_now + logger = structlog.get_logger() @@ -33,5 +35,19 @@ class User(BaseModel): email = CharField(unique=True, max_length=45) # full hash with encoded salt/parameters, argon2 but assume nothing password_hash = CharField(max_length=96) - created_at = DateTimeField(default=datetime.now) - updated_at = DateTimeField(default=datetime.now) + created_at = DateTimeField(default=utc_now) + updated_at = DateTimeField(default=utc_now) + + +class Session(BaseModel): + """ + A session represents a user's login session. + """ + + token = CharField(unique=True, primary_key=True, max_length=32) + user = ForeignKeyField(User, backref="sessions") + + expiry = DateTimeField() + + created_at = DateTimeField(default=utc_now) + last_used = DateTimeField(null=True) From 2390b6286484ac0d4d7474e0d8139e102487e14f Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 12:29:08 -0600 Subject: [PATCH 007/100] pnpm: Added clsx, removed @nkzw/eslint-config, updated @types/node to 22.9, @eslint 9.x --- frontend/package.json | 112 +- frontend/pnpm-lock.yaml | 2269 +++------------------------------------ 2 files changed, 222 insertions(+), 2159 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 8a34e3d..181f640 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,58 +1,58 @@ { - "name": "linkpulse", - "version": "0.0.1", - "author": "Xevion ", - "type": "module", - "engines": { - "node": ">=22.0.0", - "pnpm": ">=9.0.0" - }, - "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@ianvs/prettier-plugin-sort-imports": "^4.2.1", - "@nkzw/eslint-config": "^1.16.0", - "@swc/core": "^1.6.5", - "@types/node": "^20.14.8", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "autoprefixer": "^10.4.19", - "eslint": "^8.57.0", - "npm-run-all2": "^6.2.0", - "postcss": "^8.4.38", - "prettier": "^3.3.2", - "prettier-plugin-tailwindcss": "^0.6.5", - "tailwindcss": "^3.4.4", - "ts-node": "^10.9.2", - "typescript": "^5.5.2", - "vite": "^5.3.1", - "vitest": "^1.6.0" - }, - "scripts": { - "preinstall": "command -v git >/dev/null 2>&1 && git config core.hooksPath git-hooks || true", - "build": "vite build", - "dev:update-deps": "rm -rf pnpm-lock.yaml node_modules/ **/node_modules && pnpm install", - "dev": "vite dev", - "format": "prettier --write .", - "lint:format": "prettier --cache --check .", - "lint": "eslint --cache .", - "test": "npm-run-all --parallel tsc:check vitest:run lint lint:format", - "tsc:check": "tsc", - "vitest:run": "vitest run" - }, - "browserslist": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "pnpm": { - "updateConfig": { - "ignoreDependencies": [ - "eslint" - ] - } + "name": "linkpulse", + "version": "0.0.1", + "author": "Xevion ", + "type": "module", + "engines": { + "node": ">=22.0.0", + "pnpm": ">=9.0.0" + }, + "dependencies": { + "clsx": "^2.1.1", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@ianvs/prettier-plugin-sort-imports": "^4.2.1", + "@swc/core": "^1.6.5", + "@types/node": "^22.9.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.19", + "eslint": "^9", + "npm-run-all2": "^6.2.0", + "postcss": "^8.4.38", + "prettier": "^3.3.2", + "prettier-plugin-tailwindcss": "^0.6.5", + "tailwindcss": "^3.4.4", + "ts-node": "^10.9.2", + "typescript": "^5.5.2", + "vite": "^5.3.1", + "vitest": "^1.6.0" + }, + "scripts": { + "preinstall": "command -v git >/dev/null 2>&1 && git config core.hooksPath git-hooks || true", + "build": "vite build", + "dev:update-deps": "rm -rf pnpm-lock.yaml node_modules/ **/node_modules && pnpm install", + "dev": "vite dev", + "format": "prettier --write .", + "lint:format": "prettier --cache --check .", + "lint": "eslint --cache .", + "test": "npm-run-all --parallel tsc:check vitest:run lint lint:format", + "tsc:check": "tsc", + "vitest:run": "vitest run" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "pnpm": { + "updateConfig": { + "ignoreDependencies": [ + "eslint" + ] } - } \ No newline at end of file + } +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index a2f2d39..31dc745 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + clsx: + specifier: ^2.1.1 + version: 2.1.1 react: specifier: ^18.3.1 version: 18.3.1 @@ -18,15 +21,12 @@ importers: '@ianvs/prettier-plugin-sort-imports': specifier: ^4.2.1 version: 4.3.1(prettier@3.3.3) - '@nkzw/eslint-config': - specifier: ^1.16.0 - version: 1.18.1(eslint@8.57.1)(typescript@5.6.3) '@swc/core': specifier: ^1.6.5 version: 1.7.36 '@types/node': - specifier: ^20.14.8 - version: 20.16.11 + specifier: ^22.9.0 + version: 22.9.0 '@types/react': specifier: ^18.3.3 version: 18.3.11 @@ -35,13 +35,13 @@ importers: version: 18.3.1 '@vitejs/plugin-react': specifier: ^4.3.1 - version: 4.3.2(vite@5.4.9(@types/node@20.16.11)) + version: 4.3.2(vite@5.4.9(@types/node@22.9.0)) autoprefixer: specifier: ^10.4.19 version: 10.4.20(postcss@8.4.47) eslint: - specifier: ^8.57.0 - version: 8.57.1 + specifier: ^9 + version: 9.14.0(jiti@1.21.6) npm-run-all2: specifier: ^6.2.0 version: 6.2.3 @@ -56,19 +56,19 @@ importers: version: 0.6.8(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3))(prettier@3.3.3) tailwindcss: specifier: ^3.4.4 - version: 3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@20.16.11)(typescript@5.6.3)) + version: 3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3)) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.7.36)(@types/node@20.16.11)(typescript@5.6.3) + version: 10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3) typescript: specifier: ^5.5.2 version: 5.6.3 vite: specifier: ^5.3.1 - version: 5.4.9(@types/node@20.16.11) + version: 5.4.9(@types/node@22.9.0) vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@20.16.11) + version: 1.6.0(@types/node@22.9.0) packages: @@ -315,30 +315,53 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.11.1': - resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/config-array@0.18.0': + resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/core@0.7.0': + resolution: {integrity: sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead + '@eslint/eslintrc@3.1.0': + resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.14.0': + resolution: {integrity: sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.4': + resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.2': + resolution: {integrity: sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.1': + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + engines: {node: '>=18.18'} '@ianvs/prettier-plugin-sort-imports@4.3.1': resolution: {integrity: sha512-ZHwbyjkANZOjaBm3ZosADD2OUYGFzQGxfy67HmGZU94mHqe7g1LCMA7YYKB1Cq+UTPCBqlAYapY0KXAjKEw8Sg==} @@ -378,16 +401,6 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@nkzw/eslint-config@1.18.1': - resolution: {integrity: sha512-dpVQ6OPHD2/tU8K9ThMBXCmSIUfgessxH8/iCnzQrjyTmFnMNJhIZMsE0DqnIfFFdhf7Fu6dZkvKmHllzi2/bg==} - peerDependencies: - eslint: '>= 8' - - '@nkzw/eslint-plugin@1.8.0': - resolution: {integrity: sha512-doODSLfA7C6fA/wF20uk9whJIeH0ZydyVb8PlhDBZE0kJaOUSKfZqTKzxflxPF9sGG+Lhr2v/Ih//dzBHp/rxw==} - peerDependencies: - eslint: '>= 8' - '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -400,10 +413,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@nolyfill/is-core-module@1.0.39': - 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'} @@ -488,9 +497,6 @@ packages: cpu: [x64] os: [win32] - '@rtsao/scc@1.1.0': - resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -599,14 +605,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - - '@types/node@20.16.11': - resolution: {integrity: sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==} - - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/node@22.9.0': + resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} '@types/prop-types@15.7.13': resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} @@ -617,102 +617,6 @@ packages: '@types/react@18.3.11': resolution: {integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==} - '@types/semver@7.5.8': - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - - '@typescript-eslint/eslint-plugin@8.9.0': - resolution: {integrity: sha512-Y1n621OCy4m7/vTXNlCbMVp87zSd7NH0L9cXD8aIpOaNlzeWxIK4+Q19A68gSmTNRZn92UjocVUWDthGxtqHFg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/experimental-utils@5.62.0': - resolution: {integrity: sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - - '@typescript-eslint/parser@8.9.0': - resolution: {integrity: sha512-U+BLn2rqTTHnc4FL3FJjxaXptTxmf9sNftJK62XLz4+GxG3hLHm/SUNaaXP5Y4uTiuYoL5YLy4JBCJe3+t8awQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/scope-manager@5.62.0': - resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@typescript-eslint/scope-manager@8.9.0': - resolution: {integrity: sha512-bZu9bUud9ym1cabmOYH9S6TnbWRzpklVmwqICeOulTCZ9ue2/pczWzQvt/cGj2r2o1RdKoZbuEMalJJSYw3pHQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/type-utils@8.9.0': - resolution: {integrity: sha512-JD+/pCqlKqAk5961vxCluK+clkppHY07IbV3vett97KOV+8C6l+CPEPwpUuiMwgbOz/qrN3Ke4zzjqbT+ls+1Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/types@5.62.0': - resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@typescript-eslint/types@8.9.0': - resolution: {integrity: sha512-SjgkvdYyt1FAPhU9c6FiYCXrldwYYlIQLkuc+LfAhCna6ggp96ACncdtlbn8FmnG72tUkXclrDExOpEYf1nfJQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@5.62.0': - resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/typescript-estree@8.9.0': - resolution: {integrity: sha512-9iJYTgKLDG6+iqegehc5+EqE6sqaee7kb8vWpmHZ86EqwDjmlqNNHeqDVqb9duh+BY6WCNHfIGvuVU3Tf9Db0g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/utils@5.62.0': - resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - - '@typescript-eslint/utils@8.9.0': - resolution: {integrity: sha512-PKgMmaSo/Yg/F7kIZvrgrWa1+Vwn036CdNUvYFEkYbPwOH4i8xvkaRlu148W3vtheWK9ckKRIz7PBP5oUlkrvQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - - '@typescript-eslint/visitor-keys@5.62.0': - resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@typescript-eslint/visitor-keys@8.9.0': - resolution: {integrity: sha512-Ht4y38ubk4L5/U8xKUBfKNYGmvKvA1CANoxiTRMM+tOLk3lbF3DvzZCxJCRSE+2GdCMSh6zq9VZJc3asc1XuAA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@vitejs/plugin-react@4.3.2': resolution: {integrity: sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -743,13 +647,13 @@ packages: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} - acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} hasBin: true - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true @@ -796,42 +700,6 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} - engines: {node: '>= 0.4'} - - array-includes@3.1.8: - resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} - engines: {node: '>= 0.4'} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - - array.prototype.findlast@1.2.5: - resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} - engines: {node: '>= 0.4'} - - array.prototype.findlastindex@1.2.5: - resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} - engines: {node: '>= 0.4'} - - array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} - engines: {node: '>= 0.4'} - - array.prototype.flatmap@1.3.2: - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} - engines: {node: '>= 0.4'} - - array.prototype.tosorted@1.1.4: - resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} - engines: {node: '>= 0.4'} - - arraybuffer.prototype.slice@1.0.3: - resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} - engines: {node: '>= 0.4'} - assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} @@ -842,10 +710,6 @@ packages: peerDependencies: postcss: ^8.1.0 - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -868,18 +732,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - builtin-modules@3.3.0: - resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} - engines: {node: '>=6'} - cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} - engines: {node: '>= 0.4'} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -910,13 +766,9 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - ci-info@4.0.0: - resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} - engines: {node: '>=8'} - - clean-regexp@1.0.0: - resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} - engines: {node: '>=4'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -944,9 +796,6 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - core-js-compat@3.38.1: - resolution: {integrity: sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==} - create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -962,26 +811,6 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - data-view-buffer@1.0.1: - resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} - engines: {node: '>= 0.4'} - - data-view-byte-length@1.0.1: - resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} - engines: {node: '>= 0.4'} - - data-view-byte-offset@1.0.0: - resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} - engines: {node: '>= 0.4'} - - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -998,14 +827,6 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -1017,21 +838,9 @@ packages: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1044,44 +853,6 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - enhanced-resolve@5.17.1: - resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} - engines: {node: '>=10.13.0'} - - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - - es-abstract@1.23.3: - resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} - engines: {node: '>= 0.4'} - - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-iterator-helpers@1.1.0: - resolution: {integrity: sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.0.0: - resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} - engines: {node: '>= 0.4'} - - es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} - - es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} - esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -1099,136 +870,31 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - - eslint-import-resolver-typescript@3.6.3: - resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' - eslint-plugin-import-x: '*' - peerDependenciesMeta: - eslint-plugin-import: - optional: true - eslint-plugin-import-x: - optional: true - - eslint-module-utils@2.12.0: - resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - - eslint-plugin-import@2.31.0: - resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - - eslint-plugin-no-only-tests@3.3.0: - resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} - engines: {node: '>=5.0.0'} - - eslint-plugin-react-hooks@4.6.2: - resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - - eslint-plugin-react@7.37.1: - resolution: {integrity: sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - - eslint-plugin-sort-destructure-keys@2.0.0: - resolution: {integrity: sha512-4w1UQCa3o/YdfWaLr9jY8LfGowwjwjmwClyFLxIsToiyIdZMq3x9Ti44nDn34DtTPP7PWg96tUONKVmATKhYGQ==} - engines: {node: '>=12'} - peerDependencies: - eslint: 5 - 9 - - eslint-plugin-sort-keys-fix@1.1.2: - resolution: {integrity: sha512-DNPHFGCA0/hZIsfODbeLZqaGY/+q3vgtshF85r+YWDNCQ2apd9PNs/zL6ttKm0nD1IFwvxyg3YOTI7FHl4unrw==} - engines: {node: '>=0.10.0'} - - eslint-plugin-typescript-sort-keys@3.3.0: - resolution: {integrity: sha512-bRW3Rc/VNdrSP9OoY5wgjjaXCOOkZKpzvl/Mk6l8Sg8CMehVIcg9K4y33l+ZcZiknpl0aR6rKusxuCJNGZWmVw==} - engines: {node: '>= 16'} - peerDependencies: - '@typescript-eslint/parser': '>=6' - eslint: ^7 || ^8 - typescript: ^3 || ^4 || ^5 - - eslint-plugin-unicorn@55.0.0: - resolution: {integrity: sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA==} - engines: {node: '>=18.18'} - peerDependencies: - eslint: '>=8.56.0' - - eslint-plugin-unused-imports@4.0.1: - resolution: {integrity: sha512-rax76s05z64uQgG9YXsWFmXrgjkaK79AvfeAWiSxhPP6RVGxeRaj4+2u+wxxu/mDy2pmJoOy1QTOEALMia2xGQ==} + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/eslint-plugin': ^8.0.0-0 - eslint: ^9.0.0 - peerDependenciesMeta: - '@typescript-eslint/eslint-plugin': - optional: true - - eslint-rule-composer@0.3.0: - resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} - engines: {node: '>=4.0.0'} - - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@1.3.0: - resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} - engines: {node: '>=4'} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.14.0: + resolution: {integrity: sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true - espree@6.2.1: - resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==} - engines: {node: '>=6.0.0'} - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} @@ -1238,10 +904,6 @@ packages: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -1273,32 +935,25 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -1306,9 +961,6 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1317,13 +969,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} - engines: {node: '>= 0.4'} - - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1331,21 +976,10 @@ packages: get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} - engines: {node: '>= 0.4'} - get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - get-symbol-description@1.0.2: - resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} - engines: {node: '>= 0.4'} - - get-tsconfig@4.8.1: - resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1358,42 +992,14 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - globals@15.11.0: - resolution: {integrity: sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -1402,28 +1008,10 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} - engines: {node: '>= 0.4'} - - has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -1440,154 +1028,37 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - internal-slot@1.0.7: - resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} - engines: {node: '>= 0.4'} - - is-array-buffer@3.0.4: - resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} - engines: {node: '>= 0.4'} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-async-function@2.0.0: - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} - engines: {node: '>= 0.4'} - - is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} - - is-builtin-module@3.2.1: - resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} - engines: {node: '>=6'} - - is-bun-module@1.2.1: - resolution: {integrity: sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==} - - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - is-core-module@2.15.1: resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} engines: {node: '>= 0.4'} - is-data-view@1.0.1: - resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} - engines: {node: '>= 0.4'} - - is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - is-finalizationregistry@1.0.2: - resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-generator-function@1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} - engines: {node: '>= 0.4'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - - is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - - is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} - engines: {node: '>= 0.4'} - is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} - - is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.13: - resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} - engines: {node: '>= 0.4'} - - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - - is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - - is-weakset@2.0.3: - resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} - engines: {node: '>= 0.4'} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - iterator.prototype@1.1.3: - resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==} - engines: {node: '>= 0.4'} - jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -1605,10 +1076,6 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsesc@0.5.0: - resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} - hasBin: true - jsesc@3.0.2: resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} engines: {node: '>=6'} @@ -1617,9 +1084,6 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-parse-even-better-errors@3.0.2: resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1627,25 +1091,14 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true - jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1668,10 +1121,6 @@ packages: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1717,10 +1166,6 @@ packages: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1728,9 +1173,6 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} @@ -1749,18 +1191,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - natural-compare-lite@1.4.0: - resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -1790,37 +1226,6 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - object-inspect@1.13.2: - resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} - engines: {node: '>= 0.4'} - - object.entries@1.1.8: - resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} - engines: {node: '>= 0.4'} - - object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} - - object.groupby@1.0.3: - resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} - engines: {node: '>= 0.4'} - - object.values@1.2.0: - resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} - engines: {node: '>= 0.4'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} @@ -1829,10 +1234,6 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1841,18 +1242,10 @@ packages: resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} engines: {node: '>=18'} - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -1860,18 +1253,10 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1887,10 +1272,6 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -1920,14 +1301,6 @@ packages: pkg-types@1.2.1: resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - - possible-typed-array-names@1.0.0: - resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} - engines: {node: '>= 0.4'} - postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -2037,9 +1410,6 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2052,9 +1422,6 @@ packages: peerDependencies: react: ^18.3.1 - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -2073,62 +1440,22 @@ packages: resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - - read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - reflect.getprototypeof@1.0.6: - resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} - engines: {node: '>= 0.4'} - - regexp-tree@0.1.27: - resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} - hasBin: true - - regexp.prototype.flags@1.5.3: - resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} - engines: {node: '>= 0.4'} - - regjsparser@0.10.0: - resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} - hasBin: true - - requireindex@1.2.0: - resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==} - engines: {node: '>=0.10.5'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true - resolve@2.0.0-next.5: - resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} - hasBin: true - reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - rollup@4.24.0: resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2137,21 +1464,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - safe-array-concat@1.1.2: - resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} - engines: {node: '>=0.4'} - - safe-regex-test@1.0.3: - resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} - engines: {node: '>= 0.4'} - scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2161,14 +1476,6 @@ packages: engines: {node: '>=10'} hasBin: true - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2180,10 +1487,6 @@ packages: shell-quote@1.8.1: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} - side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} - engines: {node: '>= 0.4'} - siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -2191,26 +1494,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.20: - resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -2225,24 +1512,6 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string.prototype.matchall@4.0.11: - resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} - engines: {node: '>= 0.4'} - - string.prototype.repeat@1.0.0: - resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - - string.prototype.trim@1.2.9: - resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.8: - resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2251,18 +1520,10 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -2292,10 +1553,6 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} - text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -2325,12 +1582,6 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - ts-api-utils@1.3.0: - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -2348,18 +1599,6 @@ packages: '@swc/wasm': optional: true - tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - - tsutils@3.21.0: - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2368,34 +1607,6 @@ packages: resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} engines: {node: '>=4'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - - type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - - typed-array-buffer@1.0.2: - resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.1: - resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.2: - resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.6: - resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} - engines: {node: '>= 0.4'} - typescript@5.6.3: resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} @@ -2404,9 +1615,6 @@ packages: ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} - unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} @@ -2425,9 +1633,6 @@ packages: v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - vite-node@1.6.0: resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2489,21 +1694,6 @@ packages: jsdom: optional: true - which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - - which-builtin-type@1.1.4: - resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-typed-array@1.1.15: - resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} - engines: {node: '>= 0.4'} - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2526,9 +1716,6 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -2755,19 +1942,29 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': + '@eslint-community/eslint-utils@4.4.0(eslint@9.14.0(jiti@1.21.6))': dependencies: - eslint: 8.57.1 + eslint: 9.14.0(jiti@1.21.6) eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.1': {} + '@eslint-community/regexpp@4.12.1': {} - '@eslint/eslintrc@2.1.4': + '@eslint/config-array@0.18.0': + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.7.0': {} + + '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 debug: 4.3.7 - espree: 9.6.1 - globals: 13.24.0 + espree: 10.3.0 + globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -2776,19 +1973,26 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@8.57.1': {} + '@eslint/js@9.14.0': {} - '@humanwhocodes/config-array@0.13.0': + '@eslint/object-schema@2.1.4': {} + + '@eslint/plugin-kit@0.2.2': dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/object-schema@2.0.3': {} + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.1': {} '@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3)': dependencies: @@ -2837,33 +2041,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@nkzw/eslint-config@1.18.1(eslint@8.57.1)(typescript@5.6.3)': - dependencies: - '@nkzw/eslint-plugin': 1.8.0(eslint@8.57.1) - '@typescript-eslint/eslint-plugin': 8.9.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/parser': 8.9.0(eslint@8.57.1)(typescript@5.6.3) - eslint: 8.57.1 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - eslint-plugin-no-only-tests: 3.3.0 - eslint-plugin-react: 7.37.1(eslint@8.57.1) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) - eslint-plugin-sort-destructure-keys: 2.0.0(eslint@8.57.1) - eslint-plugin-sort-keys-fix: 1.1.2 - eslint-plugin-typescript-sort-keys: 3.3.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) - eslint-plugin-unicorn: 55.0.0(eslint@8.57.1) - eslint-plugin-unused-imports: 4.0.1(@typescript-eslint/eslint-plugin@8.9.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1) - transitivePeerDependencies: - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - eslint-plugin-import-x - - supports-color - - typescript - - '@nkzw/eslint-plugin@1.8.0(eslint@8.57.1)': - dependencies: - eslint: 8.57.1 - '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2876,8 +2053,6 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@nolyfill/is-core-module@1.0.39': {} - '@pkgjs/parseargs@0.11.0': optional: true @@ -2929,8 +2104,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.24.0': optional: true - '@rtsao/scc@1.1.0': {} - '@sinclair/typebox@0.27.8': {} '@swc/core-darwin-arm64@1.7.36': @@ -3018,14 +2191,10 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/json5@0.0.29': {} - - '@types/node@20.16.11': + '@types/node@22.9.0': dependencies: undici-types: 6.19.8 - '@types/normalize-package-data@2.4.4': {} - '@types/prop-types@15.7.13': {} '@types/react-dom@18.3.1': @@ -3037,148 +2206,14 @@ snapshots: '@types/prop-types': 15.7.13 csstype: 3.1.3 - '@types/semver@7.5.8': {} - - '@typescript-eslint/eslint-plugin@8.9.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': - dependencies: - '@eslint-community/regexpp': 4.11.1 - '@typescript-eslint/parser': 8.9.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/scope-manager': 8.9.0 - '@typescript-eslint/type-utils': 8.9.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/utils': 8.9.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.9.0 - eslint: 8.57.1 - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/experimental-utils@5.62.0(eslint@8.57.1)(typescript@5.6.3)': - dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.6.3) - eslint: 8.57.1 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.9.0 - '@typescript-eslint/types': 8.9.0 - '@typescript-eslint/typescript-estree': 8.9.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.9.0 - debug: 4.3.7 - eslint: 8.57.1 - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@5.62.0': - dependencies: - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/visitor-keys': 5.62.0 - - '@typescript-eslint/scope-manager@8.9.0': - dependencies: - '@typescript-eslint/types': 8.9.0 - '@typescript-eslint/visitor-keys': 8.9.0 - - '@typescript-eslint/type-utils@8.9.0(eslint@8.57.1)(typescript@5.6.3)': - dependencies: - '@typescript-eslint/typescript-estree': 8.9.0(typescript@5.6.3) - '@typescript-eslint/utils': 8.9.0(eslint@8.57.1)(typescript@5.6.3) - debug: 4.3.7 - ts-api-utils: 1.3.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - eslint - - supports-color - - '@typescript-eslint/types@5.62.0': {} - - '@typescript-eslint/types@8.9.0': {} - - '@typescript-eslint/typescript-estree@5.62.0(typescript@5.6.3)': - dependencies: - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.3.7 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.6.3 - tsutils: 3.21.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/typescript-estree@8.9.0(typescript@5.6.3)': - dependencies: - '@typescript-eslint/types': 8.9.0 - '@typescript-eslint/visitor-keys': 8.9.0 - debug: 4.3.7 - fast-glob: 3.3.2 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.6.3)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.6.3) - eslint: 8.57.1 - eslint-scope: 5.1.1 - semver: 7.6.3 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/utils@8.9.0(eslint@8.57.1)(typescript@5.6.3)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.9.0 - '@typescript-eslint/types': 8.9.0 - '@typescript-eslint/typescript-estree': 8.9.0(typescript@5.6.3) - eslint: 8.57.1 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/visitor-keys@5.62.0': - dependencies: - '@typescript-eslint/types': 5.62.0 - eslint-visitor-keys: 3.4.3 - - '@typescript-eslint/visitor-keys@8.9.0': - dependencies: - '@typescript-eslint/types': 8.9.0 - eslint-visitor-keys: 3.4.3 - - '@ungap/structured-clone@1.2.0': {} - - '@vitejs/plugin-react@4.3.2(vite@5.4.9(@types/node@20.16.11))': + '@vitejs/plugin-react@4.3.2(vite@5.4.9(@types/node@22.9.0))': dependencies: '@babel/core': 7.25.8 '@babel/plugin-transform-react-jsx-self': 7.25.7(@babel/core@7.25.8) '@babel/plugin-transform-react-jsx-source': 7.25.7(@babel/core@7.25.8) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.9(@types/node@20.16.11) + vite: 5.4.9(@types/node@22.9.0) transitivePeerDependencies: - supports-color @@ -3211,22 +2246,18 @@ snapshots: loupe: 2.3.7 pretty-format: 29.7.0 - acorn-jsx@5.3.2(acorn@7.4.1): + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: - acorn: 7.4.1 - - acorn-jsx@5.3.2(acorn@8.12.1): - dependencies: - acorn: 8.12.1 + acorn: 8.14.0 acorn-walk@8.3.4: dependencies: acorn: 8.12.1 - acorn@7.4.1: {} - acorn@8.12.1: {} + acorn@8.14.0: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -3263,73 +2294,6 @@ snapshots: argparse@2.0.1: {} - array-buffer-byte-length@1.0.1: - dependencies: - call-bind: 1.0.7 - is-array-buffer: 3.0.4 - - array-includes@3.1.8: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - is-string: 1.0.7 - - array-union@2.1.0: {} - - array.prototype.findlast@1.2.5: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-shim-unscopables: 1.0.2 - - array.prototype.findlastindex@1.2.5: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-shim-unscopables: 1.0.2 - - array.prototype.flat@1.3.2: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-shim-unscopables: 1.0.2 - - array.prototype.flatmap@1.3.2: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-shim-unscopables: 1.0.2 - - array.prototype.tosorted@1.1.4: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-shim-unscopables: 1.0.2 - - arraybuffer.prototype.slice@1.0.3: - dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - is-array-buffer: 3.0.4 - is-shared-array-buffer: 1.0.3 - assertion-error@1.1.0: {} autoprefixer@10.4.20(postcss@8.4.47): @@ -3342,10 +2306,6 @@ snapshots: postcss: 8.4.47 postcss-value-parser: 4.2.0 - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.0.0 - balanced-match@1.0.2: {} binary-extensions@2.3.0: {} @@ -3370,18 +2330,8 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.0) - builtin-modules@3.3.0: {} - cac@6.7.14: {} - call-bind@1.0.7: - dependencies: - es-define-property: 1.0.0 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.2.4 - set-function-length: 1.2.2 - callsites@3.1.0: {} camelcase-css@2.0.1: {} @@ -3425,11 +2375,7 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - ci-info@4.0.0: {} - - clean-regexp@1.0.0: - dependencies: - escape-string-regexp: 1.0.5 + clsx@2.1.1: {} color-convert@1.9.3: dependencies: @@ -3451,10 +2397,6 @@ snapshots: convert-source-map@2.0.0: {} - core-js-compat@3.38.1: - dependencies: - browserslist: 4.24.0 - create-require@1.1.1: {} cross-spawn@7.0.3: @@ -3467,28 +2409,6 @@ snapshots: csstype@3.1.3: {} - data-view-buffer@1.0.1: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - - data-view-byte-length@1.0.1: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - - data-view-byte-offset@1.0.0: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - - debug@3.2.7: - dependencies: - ms: 2.1.3 - debug@4.3.7: dependencies: ms: 2.1.3 @@ -3499,38 +2419,14 @@ snapshots: deep-is@0.1.4: {} - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.0 - es-errors: 1.3.0 - gopd: 1.0.1 - - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - didyoumean@1.2.2: {} diff-sequences@29.6.3: {} diff@4.0.2: {} - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - dlv@1.1.3: {} - doctrine@2.1.0: - dependencies: - esutils: 2.0.3 - - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - eastasianwidth@0.2.0: {} electron-to-chromium@1.5.39: {} @@ -3539,107 +2435,6 @@ snapshots: emoji-regex@9.2.2: {} - enhanced-resolve@5.17.1: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.1 - - error-ex@1.3.2: - dependencies: - is-arrayish: 0.2.1 - - es-abstract@1.23.3: - dependencies: - array-buffer-byte-length: 1.0.1 - arraybuffer.prototype.slice: 1.0.3 - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - data-view-buffer: 1.0.1 - data-view-byte-length: 1.0.1 - data-view-byte-offset: 1.0.0 - es-define-property: 1.0.0 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-set-tostringtag: 2.0.3 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.6 - get-intrinsic: 1.2.4 - get-symbol-description: 1.0.2 - globalthis: 1.0.4 - gopd: 1.0.1 - has-property-descriptors: 1.0.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - hasown: 2.0.2 - internal-slot: 1.0.7 - is-array-buffer: 3.0.4 - is-callable: 1.2.7 - is-data-view: 1.0.1 - is-negative-zero: 2.0.3 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.3 - is-string: 1.0.7 - is-typed-array: 1.1.13 - is-weakref: 1.0.2 - object-inspect: 1.13.2 - object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.3 - safe-array-concat: 1.1.2 - safe-regex-test: 1.0.3 - string.prototype.trim: 1.2.9 - string.prototype.trimend: 1.0.8 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.2 - typed-array-byte-length: 1.0.1 - typed-array-byte-offset: 1.0.2 - typed-array-length: 1.0.6 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.15 - - es-define-property@1.0.0: - dependencies: - get-intrinsic: 1.2.4 - - es-errors@1.3.0: {} - - es-iterator-helpers@1.1.0: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-set-tostringtag: 2.0.3 - function-bind: 1.1.2 - get-intrinsic: 1.2.4 - globalthis: 1.0.4 - has-property-descriptors: 1.0.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - internal-slot: 1.0.7 - iterator.prototype: 1.1.3 - safe-array-concat: 1.1.2 - - es-object-atoms@1.0.0: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.0.3: - dependencies: - get-intrinsic: 1.2.4 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - es-shim-unscopables@1.0.2: - dependencies: - hasown: 2.0.2 - - es-to-primitive@1.2.1: - dependencies: - is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 - esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -3672,221 +2467,62 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-import-resolver-node@0.3.9: - dependencies: - debug: 3.2.7 - is-core-module: 2.15.1 - resolve: 1.22.8 - transitivePeerDependencies: - - supports-color - - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1): - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.3.7 - enhanced-resolve: 5.17.1 - eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - fast-glob: 3.3.2 - get-tsconfig: 4.8.1 - is-bun-module: 1.2.1 - is-glob: 4.0.3 - optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - supports-color - - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 8.9.0(eslint@8.57.1)(typescript@5.6.3) - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1) - transitivePeerDependencies: - - supports-color - - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - hasown: 2.0.2 - is-core-module: 2.15.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.0 - semver: 6.3.1 - string.prototype.trimend: 1.0.8 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.9.0(eslint@8.57.1)(typescript@5.6.3) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-no-only-tests@3.3.0: {} - - eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): - dependencies: - eslint: 8.57.1 - - eslint-plugin-react@7.37.1(eslint@8.57.1): - dependencies: - array-includes: 3.1.8 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.2 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.1.0 - eslint: 8.57.1 - estraverse: 5.3.0 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 - object.entries: 1.1.8 - object.fromentries: 2.0.8 - object.values: 1.2.0 - prop-types: 15.8.1 - resolve: 2.0.0-next.5 - semver: 6.3.1 - string.prototype.matchall: 4.0.11 - string.prototype.repeat: 1.0.0 - - eslint-plugin-sort-destructure-keys@2.0.0(eslint@8.57.1): - dependencies: - eslint: 8.57.1 - natural-compare-lite: 1.4.0 - - eslint-plugin-sort-keys-fix@1.1.2: - dependencies: - espree: 6.2.1 - esutils: 2.0.3 - natural-compare: 1.4.0 - requireindex: 1.2.0 - - eslint-plugin-typescript-sort-keys@3.3.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3): - dependencies: - '@typescript-eslint/experimental-utils': 5.62.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/parser': 8.9.0(eslint@8.57.1)(typescript@5.6.3) - eslint: 8.57.1 - json-schema: 0.4.0 - natural-compare-lite: 1.4.0 - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - - eslint-plugin-unicorn@55.0.0(eslint@8.57.1): - dependencies: - '@babel/helper-validator-identifier': 7.25.7 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) - ci-info: 4.0.0 - clean-regexp: 1.0.0 - core-js-compat: 3.38.1 - eslint: 8.57.1 - esquery: 1.6.0 - globals: 15.11.0 - indent-string: 4.0.0 - is-builtin-module: 3.2.1 - jsesc: 3.0.2 - pluralize: 8.0.0 - read-pkg-up: 7.0.1 - regexp-tree: 0.1.27 - regjsparser: 0.10.0 - semver: 7.6.3 - strip-indent: 3.0.0 - - eslint-plugin-unused-imports@4.0.1(@typescript-eslint/eslint-plugin@8.9.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1): - dependencies: - eslint: 8.57.1 - eslint-rule-composer: 0.3.0 - optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.9.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) - - eslint-rule-composer@0.3.0: {} - - eslint-scope@5.1.1: - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - - eslint-scope@7.2.2: + eslint-scope@8.2.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-visitor-keys@1.3.0: {} - eslint-visitor-keys@3.4.3: {} - eslint@8.57.1: + eslint-visitor-keys@4.2.0: {} + + eslint@9.14.0(jiti@1.21.6): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) - '@eslint-community/regexpp': 4.11.1 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.14.0(jiti@1.21.6)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.18.0 + '@eslint/core': 0.7.0 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.14.0 + '@eslint/plugin-kit': 0.2.2 + '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 + '@humanwhocodes/retry': 0.4.1 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 debug: 4.3.7 - doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 + file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - strip-ansi: 6.0.1 text-table: 0.2.0 + optionalDependencies: + jiti: 1.21.6 transitivePeerDependencies: - supports-color - espree@6.2.1: + espree@10.3.0: dependencies: - acorn: 7.4.1 - acorn-jsx: 5.3.2(acorn@7.4.1) - eslint-visitor-keys: 1.3.0 - - espree@9.6.1: - dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) - eslint-visitor-keys: 3.4.3 + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 esquery@1.6.0: dependencies: @@ -3896,8 +2532,6 @@ snapshots: dependencies: estraverse: 5.3.0 - estraverse@4.3.0: {} - estraverse@5.3.0: {} estree-walker@3.0.3: @@ -3936,36 +2570,26 @@ snapshots: dependencies: reusify: 1.0.4 - file-entry-cache@6.0.1: + file-entry-cache@8.0.0: dependencies: - flat-cache: 3.2.0 + flat-cache: 4.0.1 fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - find-up@4.1.0: - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - find-up@5.0.0: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - flat-cache@3.2.0: + flat-cache@4.0.1: dependencies: flatted: 3.3.1 keyv: 4.5.4 - rimraf: 3.0.2 flatted@3.3.1: {} - for-each@0.3.3: - dependencies: - is-callable: 1.2.7 - foreground-child@3.3.0: dependencies: cross-spawn: 7.0.3 @@ -3973,46 +2597,17 @@ snapshots: fraction.js@4.3.7: {} - fs.realpath@1.0.0: {} - fsevents@2.3.3: optional: true function-bind@1.1.2: {} - function.prototype.name@1.1.6: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - functions-have-names: 1.2.3 - - functions-have-names@1.2.3: {} - gensync@1.0.0-beta.2: {} get-func-name@2.0.2: {} - get-intrinsic@1.2.4: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - hasown: 2.0.2 - get-stream@8.0.1: {} - get-symbol-description@1.0.2: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - - get-tsconfig@4.8.1: - dependencies: - resolve-pkg-maps: 1.0.0 - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -4030,69 +2625,18 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - globals@11.12.0: {} - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - - globals@15.11.0: {} - - globalthis@1.0.4: - dependencies: - define-properties: 1.2.1 - gopd: 1.0.1 - - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - - gopd@1.0.1: - dependencies: - get-intrinsic: 1.2.4 - - graceful-fs@4.2.11: {} - - graphemer@1.4.0: {} - - has-bigints@1.0.2: {} + globals@14.0.0: {} has-flag@3.0.0: {} has-flag@4.0.0: {} - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.0 - - has-proto@1.0.3: {} - - has-symbols@1.0.3: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.0.3 - hasown@2.0.2: dependencies: function-bind: 1.1.2 - hosted-git-info@2.8.9: {} - human-signals@5.0.0: {} ignore@5.3.2: {} @@ -4104,143 +2648,28 @@ snapshots: imurmurhash@0.1.4: {} - indent-string@4.0.0: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - internal-slot@1.0.7: - dependencies: - es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.0.6 - - is-array-buffer@3.0.4: - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - - is-arrayish@0.2.1: {} - - is-async-function@2.0.0: - dependencies: - has-tostringtag: 1.0.2 - - is-bigint@1.0.4: - dependencies: - has-bigints: 1.0.2 - is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - is-boolean-object@1.1.2: - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - - is-builtin-module@3.2.1: - dependencies: - builtin-modules: 3.3.0 - - is-bun-module@1.2.1: - dependencies: - semver: 7.6.3 - - is-callable@1.2.7: {} - is-core-module@2.15.1: dependencies: hasown: 2.0.2 - is-data-view@1.0.1: - dependencies: - is-typed-array: 1.1.13 - - is-date-object@1.0.5: - dependencies: - has-tostringtag: 1.0.2 - is-extglob@2.1.1: {} - is-finalizationregistry@1.0.2: - dependencies: - call-bind: 1.0.7 - is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.0.10: - dependencies: - has-tostringtag: 1.0.2 - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - is-map@2.0.3: {} - - is-negative-zero@2.0.3: {} - - is-number-object@1.0.7: - dependencies: - has-tostringtag: 1.0.2 - is-number@7.0.0: {} - is-path-inside@3.0.3: {} - - is-regex@1.1.4: - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - - is-set@2.0.3: {} - - is-shared-array-buffer@1.0.3: - dependencies: - call-bind: 1.0.7 - is-stream@3.0.0: {} - is-string@1.0.7: - dependencies: - has-tostringtag: 1.0.2 - - is-symbol@1.0.4: - dependencies: - has-symbols: 1.0.3 - - is-typed-array@1.1.13: - dependencies: - which-typed-array: 1.1.15 - - is-weakmap@2.0.2: {} - - is-weakref@1.0.2: - dependencies: - call-bind: 1.0.7 - - is-weakset@2.0.3: - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - - isarray@2.0.5: {} - isexe@2.0.0: {} - iterator.prototype@1.1.3: - dependencies: - define-properties: 1.2.1 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - reflect.getprototypeof: 1.0.6 - set-function-name: 2.0.2 - jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -4257,35 +2686,18 @@ snapshots: dependencies: argparse: 2.0.1 - jsesc@0.5.0: {} - jsesc@3.0.2: {} json-buffer@3.0.1: {} - json-parse-even-better-errors@2.3.1: {} - json-parse-even-better-errors@3.0.2: {} json-schema-traverse@0.4.1: {} - json-schema@0.4.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} - json5@1.0.2: - dependencies: - minimist: 1.2.8 - json5@2.2.3: {} - jsx-ast-utils@3.3.5: - dependencies: - array-includes: 3.1.8 - array.prototype.flat: 1.3.2 - object.assign: 4.1.5 - object.values: 1.2.0 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -4306,10 +2718,6 @@ snapshots: mlly: 1.7.2 pkg-types: 1.2.1 - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 - locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -4349,8 +2757,6 @@ snapshots: mimic-fn@4.0.0: {} - min-indent@1.0.1: {} - minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -4359,8 +2765,6 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minimist@1.2.8: {} - minipass@7.1.2: {} mlly@1.7.2: @@ -4380,19 +2784,10 @@ snapshots: nanoid@3.3.7: {} - natural-compare-lite@1.4.0: {} - natural-compare@1.4.0: {} node-releases@2.0.18: {} - normalize-package-data@2.5.0: - dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.8 - semver: 5.7.2 - validate-npm-package-license: 3.0.4 - normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -4417,46 +2812,6 @@ snapshots: object-hash@3.0.0: {} - object-inspect@1.13.2: {} - - object-keys@1.1.1: {} - - object.assign@4.1.5: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - has-symbols: 1.0.3 - object-keys: 1.1.1 - - object.entries@1.1.8: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - - object.fromentries@2.0.8: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - - object.groupby@1.0.3: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - - object.values@1.2.0: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - onetime@6.0.0: dependencies: mimic-fn: 4.0.0 @@ -4470,10 +2825,6 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -4482,33 +2833,18 @@ snapshots: dependencies: yocto-queue: 1.1.1 - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - p-locate@5.0.0: dependencies: p-limit: 3.1.0 - p-try@2.2.0: {} - package-json-from-dist@1.0.1: {} parent-module@1.0.1: dependencies: callsites: 3.1.0 - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.25.7 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - path-exists@4.0.0: {} - path-is-absolute@1.0.1: {} - path-key@3.1.1: {} path-key@4.0.0: {} @@ -4520,8 +2856,6 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - path-type@4.0.0: {} - pathe@1.1.2: {} pathval@1.1.1: {} @@ -4542,10 +2876,6 @@ snapshots: mlly: 1.7.2 pathe: 1.1.2 - pluralize@8.0.0: {} - - possible-typed-array-names@1.0.0: {} - postcss-import@15.1.0(postcss@8.4.47): dependencies: postcss: 8.4.47 @@ -4558,13 +2888,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.4.47 - postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@20.16.11)(typescript@5.6.3)): + postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3)): dependencies: lilconfig: 3.1.2 yaml: 2.6.0 optionalDependencies: postcss: 8.4.47 - ts-node: 10.9.2(@swc/core@1.7.36)(@types/node@20.16.11)(typescript@5.6.3) + ts-node: 10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3) postcss-nested@6.2.0(postcss@8.4.47): dependencies: @@ -4600,12 +2930,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -4616,8 +2940,6 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-is@16.13.1: {} - react-is@18.3.1: {} react-refresh@0.14.2: {} @@ -4635,70 +2957,20 @@ snapshots: json-parse-even-better-errors: 3.0.2 npm-normalize-package-bin: 3.0.1 - read-pkg-up@7.0.1: - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 - - read-pkg@5.2.0: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 2.5.0 - parse-json: 5.2.0 - type-fest: 0.6.0 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 - reflect.getprototypeof@1.0.6: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - globalthis: 1.0.4 - which-builtin-type: 1.1.4 - - regexp-tree@0.1.27: {} - - regexp.prototype.flags@1.5.3: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-errors: 1.3.0 - set-function-name: 2.0.2 - - regjsparser@0.10.0: - dependencies: - jsesc: 0.5.0 - - requireindex@1.2.0: {} - resolve-from@4.0.0: {} - resolve-pkg-maps@1.0.0: {} - resolve@1.22.8: dependencies: is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@2.0.0-next.5: - dependencies: - is-core-module: 2.15.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - reusify@1.0.4: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - rollup@4.24.0: dependencies: '@types/estree': 1.0.6 @@ -4725,45 +2997,14 @@ snapshots: dependencies: queue-microtask: 1.2.3 - safe-array-concat@1.1.2: - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - isarray: 2.0.5 - - safe-regex-test@1.0.3: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-regex: 1.1.4 - scheduler@0.23.2: dependencies: loose-envify: 1.4.0 - semver@5.7.2: {} - semver@6.3.1: {} semver@7.6.3: {} - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.2.4 - gopd: 1.0.1 - has-property-descriptors: 1.0.2 - - set-function-name@2.0.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -4772,35 +3013,12 @@ snapshots: shell-quote@1.8.1: {} - side-channel@1.0.6: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - object-inspect: 1.13.2 - siginfo@2.0.0: {} signal-exit@4.1.0: {} - slash@3.0.0: {} - source-map-js@1.2.1: {} - spdx-correct@3.2.0: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.20 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.20 - - spdx-license-ids@3.0.20: {} - stackback@0.0.2: {} std-env@3.7.0: {} @@ -4817,45 +3035,6 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string.prototype.matchall@4.0.11: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - gopd: 1.0.1 - has-symbols: 1.0.3 - internal-slot: 1.0.7 - regexp.prototype.flags: 1.5.3 - set-function-name: 2.0.2 - side-channel: 1.0.6 - - string.prototype.repeat@1.0.0: - dependencies: - define-properties: 1.2.1 - es-abstract: 1.23.3 - - string.prototype.trim@1.2.9: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - - string.prototype.trimend@1.0.8: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - - string.prototype.trimstart@1.0.8: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -4864,14 +3043,8 @@ snapshots: dependencies: ansi-regex: 6.1.0 - strip-bom@3.0.0: {} - strip-final-newline@3.0.0: {} - strip-indent@3.0.0: - dependencies: - min-indent: 1.0.1 - strip-json-comments@3.1.1: {} strip-literal@2.1.0: @@ -4898,7 +3071,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tailwindcss@3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@20.16.11)(typescript@5.6.3)): + tailwindcss@3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -4917,7 +3090,7 @@ snapshots: postcss: 8.4.47 postcss-import: 15.1.0(postcss@8.4.47) postcss-js: 4.0.1(postcss@8.4.47) - postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@20.16.11)(typescript@5.6.3)) + postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3)) postcss-nested: 6.2.0(postcss@8.4.47) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -4925,8 +3098,6 @@ snapshots: transitivePeerDependencies: - ts-node - tapable@2.2.1: {} - text-table@0.2.0: {} thenify-all@1.6.0: @@ -4949,20 +3120,16 @@ snapshots: dependencies: is-number: 7.0.0 - ts-api-utils@1.3.0(typescript@5.6.3): - dependencies: - typescript: 5.6.3 - ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@swc/core@1.7.36)(@types/node@20.16.11)(typescript@5.6.3): + ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.16.11 + '@types/node': 22.9.0 acorn: 8.12.1 acorn-walk: 8.3.4 arg: 4.1.3 @@ -4975,75 +3142,16 @@ snapshots: optionalDependencies: '@swc/core': 1.7.36 - tsconfig-paths@3.15.0: - dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 - - tslib@1.14.1: {} - - tsutils@3.21.0(typescript@5.6.3): - dependencies: - tslib: 1.14.1 - typescript: 5.6.3 - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 type-detect@4.1.0: {} - type-fest@0.20.2: {} - - type-fest@0.6.0: {} - - type-fest@0.8.1: {} - - typed-array-buffer@1.0.2: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-typed-array: 1.1.13 - - typed-array-byte-length@1.0.1: - dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - - typed-array-byte-offset@1.0.2: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - - typed-array-length@1.0.6: - dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - possible-typed-array-names: 1.0.0 - typescript@5.6.3: {} ufo@1.5.4: {} - unbox-primitive@1.0.2: - dependencies: - call-bind: 1.0.7 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 - undici-types@6.19.8: {} update-browserslist-db@1.1.1(browserslist@4.24.0): @@ -5060,18 +3168,13 @@ snapshots: v8-compile-cache-lib@3.0.1: {} - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - - vite-node@1.6.0(@types/node@20.16.11): + vite-node@1.6.0(@types/node@22.9.0): dependencies: cac: 6.7.14 debug: 4.3.7 pathe: 1.1.2 picocolors: 1.1.0 - vite: 5.4.9(@types/node@20.16.11) + vite: 5.4.9(@types/node@22.9.0) transitivePeerDependencies: - '@types/node' - less @@ -5083,16 +3186,16 @@ snapshots: - supports-color - terser - vite@5.4.9(@types/node@20.16.11): + vite@5.4.9(@types/node@22.9.0): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.24.0 optionalDependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 fsevents: 2.3.3 - vitest@1.6.0(@types/node@20.16.11): + vitest@1.6.0(@types/node@22.9.0): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -5111,11 +3214,11 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.4.9(@types/node@20.16.11) - vite-node: 1.6.0(@types/node@20.16.11) + vite: 5.4.9(@types/node@22.9.0) + vite-node: 1.6.0(@types/node@22.9.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 transitivePeerDependencies: - less - lightningcss @@ -5126,44 +3229,6 @@ snapshots: - supports-color - terser - which-boxed-primitive@1.0.2: - dependencies: - is-bigint: 1.0.4 - is-boolean-object: 1.1.2 - is-number-object: 1.0.7 - is-string: 1.0.7 - is-symbol: 1.0.4 - - which-builtin-type@1.1.4: - dependencies: - function.prototype.name: 1.1.6 - has-tostringtag: 1.0.2 - is-async-function: 2.0.0 - is-date-object: 1.0.5 - is-finalizationregistry: 1.0.2 - is-generator-function: 1.0.10 - is-regex: 1.1.4 - is-weakref: 1.0.2 - isarray: 2.0.5 - which-boxed-primitive: 1.0.2 - which-collection: 1.0.2 - which-typed-array: 1.1.15 - - which-collection@1.0.2: - dependencies: - is-map: 2.0.3 - is-set: 2.0.3 - is-weakmap: 2.0.2 - is-weakset: 2.0.3 - - which-typed-array@1.1.15: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.2 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -5187,8 +3252,6 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 - wrappy@1.0.2: {} - yallist@3.1.1: {} yaml@2.6.0: {} From ca9ea5ec308337354b8f24ef4ff861d65e4a1451 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 12:36:28 -0600 Subject: [PATCH 008/100] Updated CHANGELOG.md, updated versions to 0.3.0 --- CHANGELOG.md | 19 +++++++++++++++++++ backend/pyproject.toml | 2 +- frontend/package.json | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 281de67..1ea60ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +## Added + +- backend: `pwdlib[argon2]` for password hashing +- backend: provided `LOG_JSON_FORMAT` and `LOG_LEVEL` environment variable defaults in `run.sh` development script +- backend: `User` model, `Session` model with migration script +- backend: `utc_now` helper function +- frontend: Added `clsx` package + +## Changed + +- frontend: Updated `eslint` to `9.x.x`, `@types/node` to `22.9.x` + +## Removed + +- frontend: Removed `@nkzw/eslint-config` package +- backend: `IPAddress` Model (definition + DB state via migration) & all related code + ## [0.2.2] - 2024-11-01 ### Added diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 1725321..0a60101 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "linkpulse" -version = "0.2.2" +version = "0.3.0" description = "" authors = ["Xevion "] license = "GNU GPL v3" diff --git a/frontend/package.json b/frontend/package.json index 181f640..255c944 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "linkpulse", - "version": "0.0.1", + "version": "0.3.0", "author": "Xevion ", "type": "module", "engines": { From db260ecfa321a76a1c9348e6f6bb931e6c31060b Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 12:47:07 -0600 Subject: [PATCH 009/100] Add RELEASE_CHECKLIST.md --- CHANGELOG.md | 1 + RELEASE_CHECKLIST.md | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 RELEASE_CHECKLIST.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ea60ee..7fbd842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Added +- A release checklist to the `CHANGELOG.md` file, as a reminder for procedure. - backend: `pwdlib[argon2]` for password hashing - backend: provided `LOG_JSON_FORMAT` and `LOG_LEVEL` environment variable defaults in `run.sh` development script - backend: `User` model, `Session` model with migration script diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md new file mode 100644 index 0000000..2a2b00d --- /dev/null +++ b/RELEASE_CHECKLIST.md @@ -0,0 +1,8 @@ +# Release Checklist + +1. Ensure `backend/pyproject.toml` and `frontend/package.json` have the correct version number for the upcoming release. +2. Ensure `CHANGELOG.md` contains all changes for the upcoming release, with the correct version number and date labeled. + - `Unreleased` section should not exist. + - The version should match `pyproject.toml` and `package.json`. +3. Ensure all tests pass locally, as well as the CI/CD pipeline. +4. Correct all linting errors and warnings. From 818c3cb7bc2936406476518fbebef648c95bac09 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 13:21:50 -0600 Subject: [PATCH 010/100] Add pytest with GitHub Action workflow --- .github/workflows/test.yaml | 40 +++++++++++++++ CHANGELOG.md | 2 + backend/linkpulse/test_app.py | 16 ++++++ backend/poetry.lock | 94 ++++++++++++++++++++++++++++++++++- backend/pyproject.toml | 2 + backend/pytest.ini | 3 ++ 6 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test.yaml create mode 100644 backend/linkpulse/test_app.py create mode 100644 backend/pytest.ini diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..798cd4a --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,40 @@ +name: Pytest + +on: [pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up python + id: setup-python + uses: actions/setup-python@v5 + with: + python-version: "3.12.7" + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: 1.8.4 + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Load cached venv + id: cached-pip-wheels + uses: actions/cache@v4 + with: + path: ~/.cache + key: venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies + run: cd backend && poetry install --no-interaction --no-root + + - name: Install library + run: cd backend && poetry install --no-interaction + + - run: | + source $VENV + pytest --version diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fbd842..9ebfe07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Added - A release checklist to the `CHANGELOG.md` file, as a reminder for procedure. +- An action workflow for invoking `pytest` - backend: `pwdlib[argon2]` for password hashing - backend: provided `LOG_JSON_FORMAT` and `LOG_LEVEL` environment variable defaults in `run.sh` development script - backend: `User` model, `Session` model with migration script - backend: `utc_now` helper function +- backend: `pytest`, simple `/health` & `/api/migrations` endpoint tests - frontend: Added `clsx` package ## Changed diff --git a/backend/linkpulse/test_app.py b/backend/linkpulse/test_app.py new file mode 100644 index 0000000..e8d4436 --- /dev/null +++ b/backend/linkpulse/test_app.py @@ -0,0 +1,16 @@ +from fastapi.testclient import TestClient + +from linkpulse.app import app + + +def test_health(): + with TestClient(app) as client: + response = client.get("/health") + assert response.status_code == 200 + assert response.json() == "OK" + + +def test_migration(): + with TestClient(app) as client: + response = client.get("/api/migration") + assert response.status_code == 200 diff --git a/backend/poetry.lock b/backend/poetry.lock index 79295c1..2ceaf35 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -640,6 +640,52 @@ files = [ {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, ] +[[package]] +name = "httpcore" +version = "1.0.6" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"}, + {file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.27.2" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, + {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + [[package]] name = "human-readable" version = "1.3.4" @@ -699,6 +745,17 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "jinxed" version = "1.3.0" @@ -929,6 +986,21 @@ tzdata = ">=2020.1" [package.extras] test = ["time-machine (>=2.6.0)"] +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "priority" version = "2.0.0" @@ -1167,6 +1239,26 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pytest" +version = "8.3.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1443,4 +1535,4 @@ h11 = ">=0.9.0,<1" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "b5e06928b75df2a2428032f6e37b43e44d42b184654770686471125770abe3da" +content-hash = "ffb962027e08510f6142c1a125d14e59fcf8708b53a38c10a40bcac38d36a684" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 0a60101..7d4716d 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -35,6 +35,8 @@ pwdlib = {extras = ["argon2"], version = "^0.2.1"} memory-profiler = "^0.61.0" bpython = "^0.24" types-pytz = "^2024.2.0.20241003" +pytest = "^8.3.3" +httpx = "^0.27.2" [build-system] requires = ["poetry-core"] diff --git a/backend/pytest.ini b/backend/pytest.ini new file mode 100644 index 0000000..c24fe5b --- /dev/null +++ b/backend/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +filterwarnings = + ignore::DeprecationWarning From f08ab043e8b6d1afbc58c35e2881ad278282f1ed Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 13:43:13 -0600 Subject: [PATCH 011/100] Fix test workflow pytest invocation --- .github/workflows/test.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 798cd4a..5cc0cff 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -35,6 +35,4 @@ jobs: - name: Install library run: cd backend && poetry install --no-interaction - - run: | - source $VENV - pytest --version + - run: cd backend && poetry run pytest \ No newline at end of file From f3c5f558b015e39af5c1774dd47c24bb83a5cfb9 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 13:43:30 -0600 Subject: [PATCH 012/100] Add checklist workflow for draft label & changelog checker --- .github/workflows/checklist.yaml | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/checklist.yaml diff --git a/.github/workflows/checklist.yaml b/.github/workflows/checklist.yaml new file mode 100644 index 0000000..6dac710 --- /dev/null +++ b/.github/workflows/checklist.yaml @@ -0,0 +1,50 @@ +name: Checklist + +on: + pull_request: + types: [opened, edited, labeled, unlabeled, ] + +jobs: + changelog: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = 'CHANGELOG.md'; + const changelog = fs.readFileSync(path, 'utf8'); + + // Check for 'UNRELEASED' + if (changelog.includes('UNRELEASED')) { + throw new Error('Changelog contains "UNRELEASED"'); + } + + // Check each line that starts with '##' for the version & date format + const lines = changelog.split('\n'); + for (const line of lines) { + if (line.startsWith('## ')) { + // Expected format: '## [X.Y.Z] - YYYY-MM-DD' + const pattern = !/^\d+\.\d+\.\d+ - \d{4}-\d{2}-\d{2}$/; + if (pattern.test(line)) { + throw new Error(`Invalid version/date format: ${line}`); + } + } + } + + draft: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/github-script@v7 + with: + script: | + const forbiddenLabels = ['draft']; + let labels = context.payload.pull_request.labels; + + if (labels.some(l => forbiddenLabels.includes(l.name))) { + throw new Error(`Forbidden labels detected: ${forbiddenLabels.join(', ')}`); + } + + \ No newline at end of file From be3c94b1a9a72eb4f3bd2eb855a0bf9bf043e2e8 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 13:45:03 -0600 Subject: [PATCH 013/100] Reduce pull_request event listens for checklist workflow --- .github/workflows/checklist.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/checklist.yaml b/.github/workflows/checklist.yaml index 6dac710..eb775ff 100644 --- a/.github/workflows/checklist.yaml +++ b/.github/workflows/checklist.yaml @@ -1,8 +1,6 @@ name: Checklist -on: - pull_request: - types: [opened, edited, labeled, unlabeled, ] +on: [pull_request] jobs: changelog: From 4bc0421b743710ac2f9f21374f96a829326b4e13 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 13:46:58 -0600 Subject: [PATCH 014/100] remove erroneous negation operator from pattern --- .github/workflows/checklist.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checklist.yaml b/.github/workflows/checklist.yaml index eb775ff..cf1626f 100644 --- a/.github/workflows/checklist.yaml +++ b/.github/workflows/checklist.yaml @@ -24,7 +24,7 @@ jobs: for (const line of lines) { if (line.startsWith('## ')) { // Expected format: '## [X.Y.Z] - YYYY-MM-DD' - const pattern = !/^\d+\.\d+\.\d+ - \d{4}-\d{2}-\d{2}$/; + const pattern = /^\d+\.\d+\.\d+ - \d{4}-\d{2}-\d{2}$/; if (pattern.test(line)) { throw new Error(`Invalid version/date format: ${line}`); } From 824a08e37deba2622a6181b46e0cfea4a0f1e498 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 13:53:44 -0600 Subject: [PATCH 015/100] use error command, functional processing, fix unreleased case sensitivity, global process --- .github/workflows/checklist.yaml | 38 +++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/.github/workflows/checklist.yaml b/.github/workflows/checklist.yaml index cf1626f..00ca953 100644 --- a/.github/workflows/checklist.yaml +++ b/.github/workflows/checklist.yaml @@ -13,22 +13,34 @@ jobs: const fs = require('fs'); const path = 'CHANGELOG.md'; const changelog = fs.readFileSync(path, 'utf8'); + const errors = []; - // Check for 'UNRELEASED' - if (changelog.includes('UNRELEASED')) { - throw new Error('Changelog contains "UNRELEASED"'); - } - // Check each line that starts with '##' for the version & date format - const lines = changelog.split('\n'); - for (const line of lines) { - if (line.startsWith('## ')) { - // Expected format: '## [X.Y.Z] - YYYY-MM-DD' - const pattern = /^\d+\.\d+\.\d+ - \d{4}-\d{2}-\d{2}$/; - if (pattern.test(line)) { - throw new Error(`Invalid version/date format: ${line}`); - } + changelog.split('\n').forEach((line, index) => { + index += 1; + + if (!line.startsWith('## ')) + return; + + if (line.toLowerCase().includes('unreleased')) { + const message = `Unreleased section found at line ${index}: ${line}`; + const command = `::error file=${path},line=${index},endLine=${index},title=Unreleased Section Found::${message}`; + console.log(command); + errors.push(message); } + + // Expected format: '## [X.Y.Z] - YYYY-MM-DD' + const pattern = /^\d+\.\d+\.\d+ - \d{4}-\d{2}-\d{2}$/; + if (pattern.test(line)) { + const message = `Invalid version/date format at line ${index}: ${line}`; + const command = `::error file=${path},line=${index},endLine=${index},title=Invalid Version/Date Format::${message}`; + console.log(command); + errors.push(message); + } + }); + + if (errors.length > 0) { + throw new Error(errors.join('\n')); } draft: From f35658e969331be0475b34d34510f74db19661b7 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 13:55:03 -0600 Subject: [PATCH 016/100] Update draft to use error command format --- .github/workflows/checklist.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/checklist.yaml b/.github/workflows/checklist.yaml index 00ca953..2ed8015 100644 --- a/.github/workflows/checklist.yaml +++ b/.github/workflows/checklist.yaml @@ -54,7 +54,10 @@ jobs: let labels = context.payload.pull_request.labels; if (labels.some(l => forbiddenLabels.includes(l.name))) { - throw new Error(`Forbidden labels detected: ${forbiddenLabels.join(', ')}`); + const message = `Forbidden labels detected: ${forbiddenLabels.join(', ')}`; + const command = `::error file=${__filename},line=1,endLine=1,title=Forbidden Labels Detected::${message}`; + console.log(command); + throw new Error(message); } \ No newline at end of file From 290c1fc85c7a70670b51308d7429de2201b35ee2 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 14:01:54 -0600 Subject: [PATCH 017/100] Add __init__ module hints, TODO, rm unused import, fix router import, authentication router stub --- backend/linkpulse/__init__.py | 0 backend/linkpulse/app.py | 3 ++- backend/linkpulse/models.py | 1 - backend/linkpulse/routers/__init__.py | 0 backend/linkpulse/routers/authentication.py | 6 ++++++ 5 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 backend/linkpulse/__init__.py create mode 100644 backend/linkpulse/routers/__init__.py create mode 100644 backend/linkpulse/routers/authentication.py diff --git a/backend/linkpulse/__init__.py b/backend/linkpulse/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/linkpulse/app.py b/backend/linkpulse/app.py index 533f831..76b3d3d 100644 --- a/backend/linkpulse/app.py +++ b/backend/linkpulse/app.py @@ -45,7 +45,7 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]: db.close() -from routers import authentication +from linkpulse.routers import authentication app = FastAPI(lifespan=lifespan, default_response_class=ORJSONResponse) app.include_router(authentication.router) @@ -74,6 +74,7 @@ app.add_middleware(CorrelationIdMiddleware) @app.get("/health") async def health(): + # TODO: Check database connection return "OK" diff --git a/backend/linkpulse/models.py b/backend/linkpulse/models.py index 26365e4..fb37492 100644 --- a/backend/linkpulse/models.py +++ b/backend/linkpulse/models.py @@ -3,7 +3,6 @@ This module defines the database models for the LinkPulse backend. It also provides a base model with database connection details. """ -from datetime import datetime from os import getenv import structlog diff --git a/backend/linkpulse/routers/__init__.py b/backend/linkpulse/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/linkpulse/routers/authentication.py b/backend/linkpulse/routers/authentication.py new file mode 100644 index 0000000..f0597cd --- /dev/null +++ b/backend/linkpulse/routers/authentication.py @@ -0,0 +1,6 @@ +from typing import Tuple, Optional + +from fastapi import APIRouter +from linkpulse.models import User, Session + +router = APIRouter() From 420c448417c4e4caae899ef57c46718030f15f39 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 14:18:43 -0600 Subject: [PATCH 018/100] Use @actions/core for error annotations, try/catch wrapper for checklist --- .github/workflows/checklist.yaml | 63 ++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/.github/workflows/checklist.yaml b/.github/workflows/checklist.yaml index 2ed8015..70363ae 100644 --- a/.github/workflows/checklist.yaml +++ b/.github/workflows/checklist.yaml @@ -10,37 +10,46 @@ jobs: - uses: actions/github-script@v7 with: script: | + const core = require('@actions/core'); const fs = require('fs'); const path = 'CHANGELOG.md'; const changelog = fs.readFileSync(path, 'utf8'); - const errors = []; + let failed = false; - // Check each line that starts with '##' for the version & date format - changelog.split('\n').forEach((line, index) => { - index += 1; + try { + // Check each line that starts with '##' for the version & date format + changelog.split('\n').forEach((line, index) => { + index += 1; - if (!line.startsWith('## ')) - return; + if (!line.startsWith('## ')) + return; - if (line.toLowerCase().includes('unreleased')) { - const message = `Unreleased section found at line ${index}: ${line}`; - const command = `::error file=${path},line=${index},endLine=${index},title=Unreleased Section Found::${message}`; - console.log(command); - errors.push(message); + if (line.toLowerCase().includes('unreleased')) { + core.error({ + title: 'Unreleased Section Found', + file: path, + startline: index, + }); + failed = true; + } + + // Expected format: '## [X.Y.Z] - YYYY-MM-DD' + const pattern = /^\d+\.\d+\.\d+ - \d{4}-\d{2}-\d{2}$/; + if (pattern.test(line)) { + core.error({ + title: 'Invalid Version/Date Format', + file: path, + startline: index, + }); + failed = true; + } + }); + + if (failed) { + core.setFailed('Changelog validation failed') } - - // Expected format: '## [X.Y.Z] - YYYY-MM-DD' - const pattern = /^\d+\.\d+\.\d+ - \d{4}-\d{2}-\d{2}$/; - if (pattern.test(line)) { - const message = `Invalid version/date format at line ${index}: ${line}`; - const command = `::error file=${path},line=${index},endLine=${index},title=Invalid Version/Date Format::${message}`; - console.log(command); - errors.push(message); - } - }); - - if (errors.length > 0) { - throw new Error(errors.join('\n')); + } catch (error) { + core.setFailed(`Exception occurred while validating changelog: ${error}`); } draft: @@ -50,14 +59,12 @@ jobs: - uses: actions/github-script@v7 with: script: | + const core = require('@actions/core') const forbiddenLabels = ['draft']; let labels = context.payload.pull_request.labels; if (labels.some(l => forbiddenLabels.includes(l.name))) { - const message = `Forbidden labels detected: ${forbiddenLabels.join(', ')}`; - const command = `::error file=${__filename},line=1,endLine=1,title=Forbidden Labels Detected::${message}`; - console.log(command); - throw new Error(message); + core.setFailed(`Forbidden labels detected: ${forbiddenLabels.join(', ')}`); } \ No newline at end of file From 1605a793201f08fe31b7eab735747f24fe3def3d Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 14:18:58 -0600 Subject: [PATCH 019/100] Add pytest-coverage-comment, use color --- .github/workflows/test.yaml | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5cc0cff..06e31d1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -35,4 +35,30 @@ jobs: - name: Install library run: cd backend && poetry install --no-interaction - - run: cd backend && poetry run pytest \ No newline at end of file + - name: Pytest + run: | + cd backend + poetry run pytest --color=yes --cov-report=term-missing:skip-covered --cov=app tests/ | tee pytest-coverage.txt + + - name: Pytest coverage comment + id: coverageComment + uses: MishaKav/pytest-coverage-comment@main + with: + pytest-coverage-path: ./pytest-coverage.txt + junitxml-path: ./pytest.xml + + - name: Check the output coverage + run: | + echo "Coverage Percentage - ${{ steps.coverageComment.outputs.coverage }}" + echo "Coverage Color - ${{ steps.coverageComment.outputs.color }}" + echo "Coverage Html - ${{ steps.coverageComment.outputs.coverageHtml }}" + echo "Summary Report - ${{ steps.coverageComment.outputs.summaryReport }}" + + echo "Coverage Warnings - ${{ steps.coverageComment.outputs.warnings }}" + + echo "Coverage Errors - ${{ steps.coverageComment.outputs.errors }}" + echo "Coverage Failures - ${{ steps.coverageComment.outputs.failures }}" + echo "Coverage Skipped - ${{ steps.coverageComment.outputs.skipped }}" + echo "Coverage Tests - ${{ steps.coverageComment.outputs.tests }}" + echo "Coverage Time - ${{ steps.coverageComment.outputs.time }}" + echo "Not Success Test Info - ${{ steps.coverageComment.outputs.notSuccessTestInfo }}" \ No newline at end of file From 26c831094213efebdc2670f47bf7dcc0d4e6250d Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 14:20:54 -0600 Subject: [PATCH 020/100] Add pytest-cov for coverage abilities --- backend/poetry.lock | 94 +++++++++++++++++++++++++++++++++++++++++- backend/pyproject.toml | 1 + 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/backend/poetry.lock b/backend/poetry.lock index 2ceaf35..8dad157 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -415,6 +415,80 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.6.4" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, + {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"}, + {file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"}, + {file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"}, + {file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"}, + {file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"}, + {file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"}, + {file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"}, + {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"}, + {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"}, + {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"}, + {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"}, + {file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"}, + {file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"}, + {file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"}, + {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"}, +] + +[package.extras] +toml = ["tomli"] + [[package]] name = "curtsies" version = "0.4.2" @@ -1259,6 +1333,24 @@ pluggy = ">=1.5,<2" [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "6.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, + {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, +] + +[package.dependencies] +coverage = {version = ">=7.5", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1535,4 +1627,4 @@ h11 = ">=0.9.0,<1" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "ffb962027e08510f6142c1a125d14e59fcf8708b53a38c10a40bcac38d36a684" +content-hash = "205f5642edc4633239c77b278f973196a2fa6c6cb62c21528388efed7cdbd406" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 7d4716d..4bbaae8 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -37,6 +37,7 @@ bpython = "^0.24" types-pytz = "^2024.2.0.20241003" pytest = "^8.3.3" httpx = "^0.27.2" +pytest-cov = "^6.0.0" [build-system] requires = ["poetry-core"] From 37f92e1eb3ab087786faf3e205f537c27ea82d56 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 14:37:49 -0600 Subject: [PATCH 021/100] fix module target for coverage, add junit XML output, set pipefail to prevent exit code hiding --- .github/workflows/test.yaml | 3 ++- .gitignore | 4 +++- .vscode/settings.json | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 06e31d1..4869bc3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -38,7 +38,8 @@ jobs: - name: Pytest run: | cd backend - poetry run pytest --color=yes --cov-report=term-missing:skip-covered --cov=app tests/ | tee pytest-coverage.txt + set -o pipefail # otherwise 'tee' will eat the exit code + poetry run pytest --color=yes --cov=linkpulse --cov-report=term-missing:skip-covered --junitxml=pytest.xml | tee pytest-coverage.txt - name: Pytest coverage comment id: coverageComment diff --git a/.gitignore b/.gitignore index 75f933a..00ccd3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .env +pytest.xml +pytest-coverage.txt # Byte-compiled / optimized / DLL files __pycache__/ @@ -161,4 +163,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 1774037..14f1520 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,8 @@ "structlog", "timestamper" ], - "python.analysis.extraPaths": ["./backend/"] + "python.analysis.extraPaths": ["./backend/"], + "[github-actions-workflow]": { + "editor.formatOnSave": false + } } From 0b69aa52dd75f62bd67c5464e3bc99a2cdce885f Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 14:40:21 -0600 Subject: [PATCH 022/100] Remove core import (already available) --- .github/workflows/checklist.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/checklist.yaml b/.github/workflows/checklist.yaml index 70363ae..bb2fbad 100644 --- a/.github/workflows/checklist.yaml +++ b/.github/workflows/checklist.yaml @@ -10,7 +10,6 @@ jobs: - uses: actions/github-script@v7 with: script: | - const core = require('@actions/core'); const fs = require('fs'); const path = 'CHANGELOG.md'; const changelog = fs.readFileSync(path, 'utf8'); @@ -59,7 +58,6 @@ jobs: - uses: actions/github-script@v7 with: script: | - const core = require('@actions/core') const forbiddenLabels = ['draft']; let labels = context.payload.pull_request.labels; From 8145f74687e901c76e5224cda90fe57b46ef5708 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 14:45:42 -0600 Subject: [PATCH 023/100] Fix core.error parameter format, update CHANGELOG.md This is really annoying, the docs state nothing about the parameter format; maybe it should be implied, but there are zero examples showing the combined message/AnnotationProperty interface with 2 parameters. --- .github/workflows/checklist.yaml | 6 ++++-- CHANGELOG.md | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/checklist.yaml b/.github/workflows/checklist.yaml index bb2fbad..a52e264 100644 --- a/.github/workflows/checklist.yaml +++ b/.github/workflows/checklist.yaml @@ -24,7 +24,8 @@ jobs: return; if (line.toLowerCase().includes('unreleased')) { - core.error({ + const message = 'Unreleased section found. Please release the changes before merging.'; + core.error(message, { title: 'Unreleased Section Found', file: path, startline: index, @@ -35,7 +36,8 @@ jobs: // Expected format: '## [X.Y.Z] - YYYY-MM-DD' const pattern = /^\d+\.\d+\.\d+ - \d{4}-\d{2}-\d{2}$/; if (pattern.test(line)) { - core.error({ + const message = 'Invalid version/date format. Expected: "## [X.Y.Z] - YYYY-MM-DD"'; + core.error(message, { title: 'Invalid Version/Date Format', file: path, startline: index, diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ebfe07..e402826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A release checklist to the `CHANGELOG.md` file, as a reminder for procedure. - An action workflow for invoking `pytest` +- Pytest coverage report generation in the CI/CD pipeline - backend: `pwdlib[argon2]` for password hashing - backend: provided `LOG_JSON_FORMAT` and `LOG_LEVEL` environment variable defaults in `run.sh` development script - backend: `User` model, `Session` model with migration script From 1b82cc762125d341dd192ea45ee8e57af7194d26 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:00:56 -0600 Subject: [PATCH 024/100] Acquire database URL from Railway, envvar injection --- .github/workflows/test.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 4869bc3..d868d02 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -34,6 +34,16 @@ jobs: - name: Install library run: cd backend && poetry install --no-interaction + + - name: Acquire Database URL from Railway + env: + RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} + SERVICE_ID: linkpulse + ENVIRONMENT_ID: development + run: | + bash <(curl -fsSL cli.new) + echo "DATABASE_URL=$(railway variables --service Postgres --environment development --json | jq .DATABASE_PUBLIC_URL)" >> "$GITHUB_ENV" + - name: Pytest run: | From 555cbc762f551cd670171c8db08d31997a120cc6 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:04:43 -0600 Subject: [PATCH 025/100] Fix jq output (-cMr), use SERVICE_ID, ENVIRONMENT_ID properly --- .github/workflows/test.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d868d02..297dc2f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -38,12 +38,11 @@ jobs: - name: Acquire Database URL from Railway env: RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} - SERVICE_ID: linkpulse + SERVICE_ID: Postgres ENVIRONMENT_ID: development run: | bash <(curl -fsSL cli.new) - echo "DATABASE_URL=$(railway variables --service Postgres --environment development --json | jq .DATABASE_PUBLIC_URL)" >> "$GITHUB_ENV" - + echo "DATABASE_URL=$(railway variables --service $SERVICE_ID --environment $ENVIRONMENT_ID --json | jq -cMr .DATABASE_PUBLIC_URL)" >> "$GITHUB_ENV" - name: Pytest run: | From 0e7fc7e25ea5841c06b6637e7a3996d66f8f118d Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:09:07 -0600 Subject: [PATCH 026/100] use add-mask for database url --- .github/workflows/test.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 297dc2f..e4a02ab 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -42,7 +42,9 @@ jobs: ENVIRONMENT_ID: development run: | bash <(curl -fsSL cli.new) - echo "DATABASE_URL=$(railway variables --service $SERVICE_ID --environment $ENVIRONMENT_ID --json | jq -cMr .DATABASE_PUBLIC_URL)" >> "$GITHUB_ENV" + DATABASE_URL=$(railway variables --service $SERVICE_ID --environment $ENVIRONMENT_ID --json | jq -cMr .DATABASE_PUBLIC_URL) + echo "::add-mask::$DATABASE_URL" + echo "DATABASE_URL=$DATABASE_URL" >> "$GITHUB_ENV" - name: Pytest run: | From 94cb0f9f5bbcf2cf98804a248e8546a2de1960c7 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:23:39 -0600 Subject: [PATCH 027/100] Fix railway confirmation blocker, disable JSON logging in test workflow, disable Install library step --- .github/workflows/test.yaml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e4a02ab..9823062 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -31,9 +31,10 @@ jobs: - name: Install dependencies run: cd backend && poetry install --no-interaction --no-root - - - name: Install library - run: cd backend && poetry install --no-interaction + + # Disable for now, remove if ultimately not needed. + # - name: Install library + # run: cd backend && poetry install --no-interaction - name: Acquire Database URL from Railway env: @@ -41,12 +42,15 @@ jobs: SERVICE_ID: Postgres ENVIRONMENT_ID: development run: | - bash <(curl -fsSL cli.new) + bash <(curl -fsSL cli.new) --verbose --yes DATABASE_URL=$(railway variables --service $SERVICE_ID --environment $ENVIRONMENT_ID --json | jq -cMr .DATABASE_PUBLIC_URL) echo "::add-mask::$DATABASE_URL" echo "DATABASE_URL=$DATABASE_URL" >> "$GITHUB_ENV" - name: Pytest + env: + LOG_LEVEL: DEBUG + LOG_JSON_FORMAT: false run: | cd backend set -o pipefail # otherwise 'tee' will eat the exit code From 78a82311a22aa31ded29eee41a2f1bc7d20489c2 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:25:33 -0600 Subject: [PATCH 028/100] Fix coverage/junit generated paths (backend) --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9823062..5c11450 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -60,8 +60,8 @@ jobs: id: coverageComment uses: MishaKav/pytest-coverage-comment@main with: - pytest-coverage-path: ./pytest-coverage.txt - junitxml-path: ./pytest.xml + pytest-coverage-path: backend/pytest-coverage.txt + junitxml-path: backend/pytest.xml - name: Check the output coverage run: | From f1be6676d1c7b9b801143c77750b0a903840cb07 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:28:11 -0600 Subject: [PATCH 029/100] Add pytest/coverage file check since pytest-coverage-comment does not error --- .github/workflows/test.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5c11450..8c77c7a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -55,9 +55,16 @@ jobs: cd backend set -o pipefail # otherwise 'tee' will eat the exit code poetry run pytest --color=yes --cov=linkpulse --cov-report=term-missing:skip-covered --junitxml=pytest.xml | tee pytest-coverage.txt + + # pytest-coverage-comment won't error if the files are missing + if [ ! -f backend/pytest-coverage.txt ] || [ ! -f backend/pytest.xml ]; then + echo "::error::Coverage files not found" + exit 1 + fi - name: Pytest coverage comment id: coverageComment + if: steps.check-coverage-files.outputs.success == 'true' uses: MishaKav/pytest-coverage-comment@main with: pytest-coverage-path: backend/pytest-coverage.txt From 53a0ac062452344fd60f3dad76975429fc706eec Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:31:38 -0600 Subject: [PATCH 030/100] Add token permissions for coverage-comment, fully qualify & use development environment in run.sh DB URL --- .github/workflows/test.yaml | 6 ++++++ backend/run.sh | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8c77c7a..80a8a55 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,6 +2,12 @@ name: Pytest on: [pull_request] +# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs +# Required by MishaKav/pytest-coverage-comment +permissions: + checks: write + pull-requests: write + jobs: test: runs-on: ubuntu-latest diff --git a/backend/run.sh b/backend/run.sh index 9423046..9577b93 100755 --- a/backend/run.sh +++ b/backend/run.sh @@ -49,7 +49,7 @@ if $DATABASE_DEFINED; then else if $RAILWAY_AVAILABLE; then if $PROJECT_LINKED; then - DATABASE_URL="$(railway variables -s Postgres --json | jq .DATABASE_PUBLIC_URL -cMr)" $COMMAND $@ + DATABASE_URL="$(railway variables --service Postgres --environment development --json | jq .DATABASE_PUBLIC_URL -cMr)" $COMMAND $@ else echo "error: Railway project not linked." echo "Run 'railway link' to link the project." From d38362819e2331c8c2403c00f20662eeb5d57825 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:33:47 -0600 Subject: [PATCH 031/100] Add contents read permission, required for checkout --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 80a8a55..5df429e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -5,6 +5,7 @@ on: [pull_request] # https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs # Required by MishaKav/pytest-coverage-comment permissions: + contents: read checks: write pull-requests: write From fcfa213eedc907f3b477c36308d20c6d44cfdf55 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:36:08 -0600 Subject: [PATCH 032/100] limit checklist invocation to CHANGELOG path --- .github/workflows/checklist.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/checklist.yaml b/.github/workflows/checklist.yaml index a52e264..6243bb6 100644 --- a/.github/workflows/checklist.yaml +++ b/.github/workflows/checklist.yaml @@ -1,6 +1,9 @@ name: Checklist -on: [pull_request] +on: + pull_request: + paths: + - 'CHANGELOG.md' jobs: changelog: From 5508b8e6f49fc0b1e387ffb50c117a4c9289b3a6 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:36:55 -0600 Subject: [PATCH 033/100] use relative path --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5df429e..19c7cee 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -64,7 +64,7 @@ jobs: poetry run pytest --color=yes --cov=linkpulse --cov-report=term-missing:skip-covered --junitxml=pytest.xml | tee pytest-coverage.txt # pytest-coverage-comment won't error if the files are missing - if [ ! -f backend/pytest-coverage.txt ] || [ ! -f backend/pytest.xml ]; then + if [ ! -f ./pytest-coverage.txt ] || [ ! -f ./pytest.xml ]; then echo "::error::Coverage files not found" exit 1 fi From 2c4e7ddfcd6b089cb745f4c3644bec478a23237b Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:41:01 -0600 Subject: [PATCH 034/100] use .venv for proper Poetry cache --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 19c7cee..5a5fd47 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -33,8 +33,8 @@ jobs: id: cached-pip-wheels uses: actions/cache@v4 with: - path: ~/.cache key: venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} + path: .venv # While ~/.cache is a fine default, I want to separate this cache from other caches - name: Install dependencies run: cd backend && poetry install --no-interaction --no-root From e76b32146982fced1b6f2b085880e2483ce09f99 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:41:26 -0600 Subject: [PATCH 035/100] Use poetry version in env, use for cache key --- .github/workflows/test.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5a5fd47..c9ac980 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -9,6 +9,9 @@ permissions: checks: write pull-requests: write +env: + POETRY_VERSION: 1.8.4 + jobs: test: runs-on: ubuntu-latest @@ -25,7 +28,7 @@ jobs: - name: Install Poetry uses: snok/install-poetry@v1 with: - version: 1.8.4 + version: ${{ env.POETRY_VERSION }} virtualenvs-create: true virtualenvs-in-project: true @@ -33,8 +36,8 @@ jobs: id: cached-pip-wheels uses: actions/cache@v4 with: - key: venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} path: .venv # While ~/.cache is a fine default, I want to separate this cache from other caches + key: venv-${{ steps.setup-python.outputs.python-version }}-${{ env.POETRY_VERSION }}-${{ hashFiles('**/poetry.lock') }} - name: Install dependencies run: cd backend && poetry install --no-interaction --no-root From 43c00056b7a2df912a8a9abbdf0408b993491f78 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:42:52 -0600 Subject: [PATCH 036/100] Remove accidentally added if check from coverageComment step --- .github/workflows/test.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c9ac980..11912bf 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -74,7 +74,6 @@ jobs: - name: Pytest coverage comment id: coverageComment - if: steps.check-coverage-files.outputs.success == 'true' uses: MishaKav/pytest-coverage-comment@main with: pytest-coverage-path: backend/pytest-coverage.txt From 295a499c92cf77e5707aef55905eab4d5dc5d7bd Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 15:47:56 -0600 Subject: [PATCH 037/100] Remove coverageHtml echo line --- .github/workflows/test.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 11912bf..44b9c39 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -83,7 +83,6 @@ jobs: run: | echo "Coverage Percentage - ${{ steps.coverageComment.outputs.coverage }}" echo "Coverage Color - ${{ steps.coverageComment.outputs.color }}" - echo "Coverage Html - ${{ steps.coverageComment.outputs.coverageHtml }}" echo "Summary Report - ${{ steps.coverageComment.outputs.summaryReport }}" echo "Coverage Warnings - ${{ steps.coverageComment.outputs.warnings }}" From 07c9a724a2ec513eb6290ea48e7364c3bf0b2f46 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 16:07:49 -0600 Subject: [PATCH 038/100] Add user soft delete ability via BitField flags, explicit on_delete CASCADE, Session.is_expired(), Session.use() --- backend/linkpulse/models.py | 40 +++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/backend/linkpulse/models.py b/backend/linkpulse/models.py index fb37492..17aba77 100644 --- a/backend/linkpulse/models.py +++ b/backend/linkpulse/models.py @@ -3,13 +3,14 @@ This module defines the database models for the LinkPulse backend. It also provides a base model with database connection details. """ +import datetime from os import getenv +from typing import Optional import structlog -from peewee import AutoField, CharField, DateTimeField, ForeignKeyField, Model -from playhouse.db_url import connect - from linkpulse.utilities import utc_now +from peewee import AutoField, CharField, DateTimeField, ForeignKeyField, BitField, Model +from playhouse.db_url import connect logger = structlog.get_logger() @@ -32,21 +33,52 @@ class User(BaseModel): id = AutoField(primary_key=True) # arbitrary max length, but statistically reasonable and limits UI concerns/abuse cases email = CharField(unique=True, max_length=45) + flags = BitField() # full hash with encoded salt/parameters, argon2 but assume nothing password_hash = CharField(max_length=96) created_at = DateTimeField(default=utc_now) updated_at = DateTimeField(default=utc_now) + # prefer soft deletes before hard deletes + deleted_at = DateTimeField(null=True) + deleted = flags.flag(1) class Session(BaseModel): """ A session represents a user's login session. + + For now, a session returned from the API implies it's validity. + In the future, sessions may be invalidated or revoked, but kept in the database AND returned. + This could allow sessions to be tracked and audited even after they are no longer valid, or allow more proper 'logout' messages. """ token = CharField(unique=True, primary_key=True, max_length=32) - user = ForeignKeyField(User, backref="sessions") + user = ForeignKeyField(User, backref="sessions", on_delete="CASCADE") expiry = DateTimeField() created_at = DateTimeField(default=utc_now) last_used = DateTimeField(null=True) + + def is_expired( + self, revoke: bool = True, now: Optional[datetime.datetime] = None + ) -> bool: + """ + Check if the session is expired. If `revoke` is True, the session will be automatically revoked if it is expired. + """ + if now is None: + now = utc_now() + + if self.expiry < now: + if revoke: + self.delete_instance() + return True + return False + + def use(self, now: Optional[datetime.datetime] = None): + """ + Update the last_used field of the session. + """ + if now is None: + now = utc_now() + self.last_used = now # type: ignore From e5e718d3e4e32f5a8cba9ee80cad0a8e16900f0c Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 16:29:46 -0600 Subject: [PATCH 039/100] migration: Session model, User.flags field --- .../005_create_session_add_user_flags.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 backend/linkpulse/migrations/005_create_session_add_user_flags.py diff --git a/backend/linkpulse/migrations/005_create_session_add_user_flags.py b/backend/linkpulse/migrations/005_create_session_add_user_flags.py new file mode 100644 index 0000000..5f79dcc --- /dev/null +++ b/backend/linkpulse/migrations/005_create_session_add_user_flags.py @@ -0,0 +1,63 @@ +"""Peewee migrations -- 005_create_session_add_user_flags.py. + +Some examples (model - class or model name):: + + > Model = migrator.orm['table_name'] # Return model in current state by name + > Model = migrator.ModelClass # Return model in current state by name + + > migrator.sql(sql) # Run custom SQL + > migrator.run(func, *args, **kwargs) # Run python function with the given args + > migrator.create_model(Model) # Create a model (could be used as decorator) + > migrator.remove_model(model, cascade=True) # Remove a model + > migrator.add_fields(model, **fields) # Add fields to a model + > migrator.change_fields(model, **fields) # Change fields + > migrator.remove_fields(model, *field_names, cascade=True) + > migrator.rename_field(model, old_field_name, new_field_name) + > migrator.rename_table(model, new_table_name) + > migrator.add_index(model, *col_names, unique=False) + > migrator.add_not_null(model, *field_names) + > migrator.add_default(model, field_name, default) + > migrator.add_constraint(model, name, sql) + > migrator.drop_index(model, *col_names) + > migrator.drop_not_null(model, *field_names) + > migrator.drop_constraints(model, *constraints) + +""" + +from contextlib import suppress + +import peewee as pw +from peewee_migrate import Migrator + + +with suppress(ImportError): + import playhouse.postgres_ext as pw_pext + + +def migrate(migrator: Migrator, database: pw.Database, *, fake=False): + """Write your migrations here.""" + + migrator.add_fields( + 'user', + + flags=pw.BitField(default=0), + deleted_at=pw.DateTimeField(null=True)) + + @migrator.create_model + class Session(pw.Model): + token = pw.CharField(max_length=32, primary_key=True) + user = pw.ForeignKeyField(column_name='user_id', field='id', model=migrator.orm['user'], on_delete='CASCADE') + expiry = pw.DateTimeField() + created_at = pw.DateTimeField() + last_used = pw.DateTimeField(null=True) + + class Meta: + table_name = "session" + + +def rollback(migrator: Migrator, database: pw.Database, *, fake=False): + """Write your rollback migrations here.""" + + migrator.remove_fields('user', 'flags', 'deleted_at') + + migrator.remove_model('session') From 692009848780adec04591db0859abd16bdc605cc Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 16:39:09 -0600 Subject: [PATCH 040/100] Add pytest-xdist for parallel testing --- backend/poetry.lock | 36 +++++++++++++++++++++++++++++++++++- backend/pyproject.toml | 1 + 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/backend/poetry.lock b/backend/poetry.lock index 8dad157..8687fda 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -549,6 +549,20 @@ files = [ {file = "cwcwidth-0.1.9.tar.gz", hash = "sha256:f19d11a0148d4a8cacd064c96e93bca8ce3415a186ae8204038f45e108db76b8"}, ] +[[package]] +name = "execnet" +version = "2.1.1" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.8" +files = [ + {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, + {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + [[package]] name = "fastapi" version = "0.100.0" @@ -1351,6 +1365,26 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +[[package]] +name = "pytest-xdist" +version = "3.6.1" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, + {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, +] + +[package.dependencies] +execnet = ">=2.1" +pytest = ">=7.0.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1627,4 +1661,4 @@ h11 = ">=0.9.0,<1" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "205f5642edc4633239c77b278f973196a2fa6c6cb62c21528388efed7cdbd406" +content-hash = "bf1c24a8867c448db911c50be70cfdf48df6b87ea39f10f66a0e34f8139f6d82" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 4bbaae8..68f9c78 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -29,6 +29,7 @@ asgi-correlation-id = "^4.3.4" orjson = "^3.10.10" hypercorn = "^0.17.3" pwdlib = {extras = ["argon2"], version = "^0.2.1"} +pytest-xdist = "^3.6.1" [tool.poetry.group.dev.dependencies] From 35d967360dc6833656c111ac6e18bf4543ef9de2 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 16:39:36 -0600 Subject: [PATCH 041/100] Integrate pytest into run.sh cleanly, use $@ args directly in COMMAND def --- backend/run.sh | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/backend/run.sh b/backend/run.sh index 9577b93..a2d2dea 100755 --- a/backend/run.sh +++ b/backend/run.sh @@ -10,7 +10,14 @@ fi export ENVIRONMENT=${ENVIRONMENT:-development} export LOG_JSON_FORMAT=${LOG_JSON_FORMAT:-false} export LOG_LEVEL=${LOG_LEVEL:-debug} -COMMAND='poetry run python3 -m linkpulse' +COMMAND='poetry run python3 -m linkpulse $@' + +# If arguments start with 'poetry run pytest' or 'pytest' use args as is +if [[ "$1" == "poetry" && "$2" == "run" && "$3" == "pytest" ]]; then + COMMAND=$@ +elif [[ "$1" == "pytest" ]]; then + COMMAND=$@ +fi # Check if Railway CLI is available RAILWAY_AVAILABLE=false @@ -45,11 +52,11 @@ if $RAILWAY_AVAILABLE; then fi if $DATABASE_DEFINED; then - $COMMAND $@ + $COMMAND else if $RAILWAY_AVAILABLE; then if $PROJECT_LINKED; then - DATABASE_URL="$(railway variables --service Postgres --environment development --json | jq .DATABASE_PUBLIC_URL -cMr)" $COMMAND $@ + DATABASE_URL="$(railway variables --service Postgres --environment development --json | jq .DATABASE_PUBLIC_URL -cMr)" $COMMAND else echo "error: Railway project not linked." echo "Run 'railway link' to link the project." From d725d1b8635a39b36384b0344fe01eafe66393e0 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 17:00:45 -0600 Subject: [PATCH 042/100] Add test_session, add randomizers, move test_app, user/session fixtures --- backend/linkpulse/tests/__init__.py | 0 backend/linkpulse/tests/random.py | 15 +++++++ backend/linkpulse/{ => tests}/test_app.py | 0 backend/linkpulse/tests/test_session.py | 51 +++++++++++++++++++++++ backend/linkpulse/tests/test_user.py | 10 +++++ 5 files changed, 76 insertions(+) create mode 100644 backend/linkpulse/tests/__init__.py create mode 100644 backend/linkpulse/tests/random.py rename backend/linkpulse/{ => tests}/test_app.py (100%) create mode 100644 backend/linkpulse/tests/test_session.py create mode 100644 backend/linkpulse/tests/test_user.py diff --git a/backend/linkpulse/tests/__init__.py b/backend/linkpulse/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/linkpulse/tests/random.py b/backend/linkpulse/tests/random.py new file mode 100644 index 0000000..ccc5495 --- /dev/null +++ b/backend/linkpulse/tests/random.py @@ -0,0 +1,15 @@ +import random +import string +import time + + +def epoch() -> int: + return int(time.time()) + + +def random_string(length: int = 10) -> str: + return "".join(random.choices(string.ascii_lowercase + string.digits, k=length)) + + +def random_email() -> str: + return random_string() + str(epoch()) + "@example.com" diff --git a/backend/linkpulse/test_app.py b/backend/linkpulse/tests/test_app.py similarity index 100% rename from backend/linkpulse/test_app.py rename to backend/linkpulse/tests/test_app.py diff --git a/backend/linkpulse/tests/test_session.py b/backend/linkpulse/tests/test_session.py new file mode 100644 index 0000000..6083686 --- /dev/null +++ b/backend/linkpulse/tests/test_session.py @@ -0,0 +1,51 @@ +from datetime import timedelta + +import pytest +import structlog +from linkpulse.models import Session +from linkpulse.tests.random import random_string +from linkpulse.tests.test_user import user +from linkpulse.utilities import utc_now + +logger = structlog.get_logger() + + +@pytest.fixture +def db(): + return Session._meta.database + + +@pytest.fixture +def session(user): + return Session.create( + user=user, token=random_string(32), expiry=utc_now() + timedelta(hours=1) + ) + + +@pytest.fixture +def expired_session(session): + session.expiry = utc_now() - timedelta(hours=1) + return session + + +def test_session_create(session): + assert Session.get_or_none(Session.token == session.token) is not None + + +def test_auto_revoke(db, expired_session): + # Expired, but still exists + assert Session.get_or_none(Session.token == expired_session.token) is not None + # Test revoke=False + assert expired_session.is_expired(revoke=False) is True + # Test revoke=True + assert expired_session.is_expired(revoke=True) is True + # Expired, and no longer exists + assert Session.get_or_none(Session.token == expired_session.token) is None + + +def test_expiry_valid(session): + assert session.is_expired() is False + + +def test_expiry_invalid(expired_session): + assert expired_session.is_expired() is True diff --git a/backend/linkpulse/tests/test_user.py b/backend/linkpulse/tests/test_user.py new file mode 100644 index 0000000..bd0f37c --- /dev/null +++ b/backend/linkpulse/tests/test_user.py @@ -0,0 +1,10 @@ +import pytest +from linkpulse.models import User +from linkpulse.tests.random import epoch, random_email, random_string + + +@pytest.fixture +def user(): + return User.create( + email=random_email(), password_hash=str(epoch()) + random_string(64) + ) From afd29806dc8b32884bd50695799334ab6cd33189 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 17:30:28 -0600 Subject: [PATCH 043/100] test utc_now, hacky fix for TZ Aware/Naive comparison when fetching datetimes --- backend/linkpulse/models.py | 6 +++++- backend/linkpulse/tests/test_utilities.py | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 backend/linkpulse/tests/test_utilities.py diff --git a/backend/linkpulse/models.py b/backend/linkpulse/models.py index 17aba77..b461b8d 100644 --- a/backend/linkpulse/models.py +++ b/backend/linkpulse/models.py @@ -60,6 +60,10 @@ class Session(BaseModel): created_at = DateTimeField(default=utc_now) last_used = DateTimeField(null=True) + @property + def expiry_utc(self) -> datetime.datetime: + return self.expiry.replace(tzinfo=datetime.timezone.utc) # type: ignore + def is_expired( self, revoke: bool = True, now: Optional[datetime.datetime] = None ) -> bool: @@ -69,7 +73,7 @@ class Session(BaseModel): if now is None: now = utc_now() - if self.expiry < now: + if self.expiry_utc < now: if revoke: self.delete_instance() return True diff --git a/backend/linkpulse/tests/test_utilities.py b/backend/linkpulse/tests/test_utilities.py new file mode 100644 index 0000000..e6d00c0 --- /dev/null +++ b/backend/linkpulse/tests/test_utilities.py @@ -0,0 +1,6 @@ +from linkpulse.utilities import utc_now + + +def test_utcnow_tz_aware(): + dt = utc_now() + dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None From ced662a7e8defee63be967925258d5d1cedc675b Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 17:30:46 -0600 Subject: [PATCH 044/100] Disable captured log, use pretty stderr output --- backend/pytest.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/pytest.ini b/backend/pytest.ini index c24fe5b..7220561 100644 --- a/backend/pytest.ini +++ b/backend/pytest.ini @@ -1,3 +1,4 @@ [pytest] filterwarnings = ignore::DeprecationWarning +addopts = --show-capture=stderr \ No newline at end of file From 591635fa7030f8ef83a94e793528fc6b7c27babd Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 17:41:10 -0600 Subject: [PATCH 045/100] Fix literal arg for migrate --- backend/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/run.sh b/backend/run.sh index a2d2dea..ca8134f 100755 --- a/backend/run.sh +++ b/backend/run.sh @@ -10,7 +10,7 @@ fi export ENVIRONMENT=${ENVIRONMENT:-development} export LOG_JSON_FORMAT=${LOG_JSON_FORMAT:-false} export LOG_LEVEL=${LOG_LEVEL:-debug} -COMMAND='poetry run python3 -m linkpulse $@' +COMMAND="poetry run python3 -m linkpulse $@" # If arguments start with 'poetry run pytest' or 'pytest' use args as is if [[ "$1" == "poetry" && "$2" == "run" && "$3" == "pytest" ]]; then From 4467195cb89bb824e567053aad861c1b78bde60f Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 17:49:49 -0600 Subject: [PATCH 046/100] Custom session constraints migration The automatic migrator wouldn't do this on it's own, I manually made this migration --- .../migrations/006_add_session_constraints.py | 63 +++++++++++++++++++ backend/linkpulse/models.py | 17 ++++- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 backend/linkpulse/migrations/006_add_session_constraints.py diff --git a/backend/linkpulse/migrations/006_add_session_constraints.py b/backend/linkpulse/migrations/006_add_session_constraints.py new file mode 100644 index 0000000..1517f13 --- /dev/null +++ b/backend/linkpulse/migrations/006_add_session_constraints.py @@ -0,0 +1,63 @@ +"""Peewee migrations -- 006_add_session_constraints.py. + +Some examples (model - class or model name):: + + > Model = migrator.orm['table_name'] # Return model in current state by name + > Model = migrator.ModelClass # Return model in current state by name + + > migrator.sql(sql) # Run custom SQL + > migrator.run(func, *args, **kwargs) # Run python function with the given args + > migrator.create_model(Model) # Create a model (could be used as decorator) + > migrator.remove_model(model, cascade=True) # Remove a model + > migrator.add_fields(model, **fields) # Add fields to a model + > migrator.change_fields(model, **fields) # Change fields + > migrator.remove_fields(model, *field_names, cascade=True) + > migrator.rename_field(model, old_field_name, new_field_name) + > migrator.rename_table(model, new_table_name) + > migrator.add_index(model, *col_names, unique=False) + > migrator.add_not_null(model, *field_names) + > migrator.add_default(model, field_name, default) + > migrator.add_constraint(model, name, sql) + > migrator.drop_index(model, *col_names) + > migrator.drop_not_null(model, *field_names) + > migrator.drop_constraints(model, *constraints) + +""" + +from contextlib import suppress + +import peewee as pw +from peewee_migrate import Migrator + + +with suppress(ImportError): + import playhouse.postgres_ext as pw_pext + + +def migrate(migrator: Migrator, database: pw.Database, *, fake=False): + """Write your migrations here.""" + + migrator.add_constraint( + "session", "session_token_length", pw.Check("LENGTH(token) = 32") + ) + + migrator.add_constraint( + "session", "session_expiry_created_at", pw.Check("expiry > created_at") + ) + + migrator.add_constraint( + "session", + "session_last_used_created_at", + pw.Check("last_used IS NULL OR last_used <= created_at"), + ) + + +def rollback(migrator: Migrator, database: pw.Database, *, fake=False): + """Write your rollback migrations here.""" + + migrator.drop_constraints( + "session", + "session_token_length", + "session_expiry_created_at", + "session_last_used_created_at", + ) diff --git a/backend/linkpulse/models.py b/backend/linkpulse/models.py index b461b8d..c9fc192 100644 --- a/backend/linkpulse/models.py +++ b/backend/linkpulse/models.py @@ -9,7 +9,15 @@ from typing import Optional import structlog from linkpulse.utilities import utc_now -from peewee import AutoField, CharField, DateTimeField, ForeignKeyField, BitField, Model +from peewee import ( + AutoField, + BitField, + CharField, + Check, + DateTimeField, + ForeignKeyField, + Model, +) from playhouse.db_url import connect logger = structlog.get_logger() @@ -60,6 +68,13 @@ class Session(BaseModel): created_at = DateTimeField(default=utc_now) last_used = DateTimeField(null=True) + class Meta: + constraints = [ + Check("LENGTH(token) = 32"), + Check("expiry > created_at"), + Check("last_used IS NULL OR last_used <= created_at"), + ] + @property def expiry_utc(self) -> datetime.datetime: return self.expiry.replace(tzinfo=datetime.timezone.utc) # type: ignore From 40e4e4f4b8189f3c4e6f56a1f0b4835c8fde3309 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 20:08:10 -0600 Subject: [PATCH 047/100] correctly add names to table constraints, fix inverted constraint for session_last_used_created --- .../linkpulse/migrations/006_add_session_constraints.py | 2 +- backend/linkpulse/models.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/backend/linkpulse/migrations/006_add_session_constraints.py b/backend/linkpulse/migrations/006_add_session_constraints.py index 1517f13..7e044de 100644 --- a/backend/linkpulse/migrations/006_add_session_constraints.py +++ b/backend/linkpulse/migrations/006_add_session_constraints.py @@ -48,7 +48,7 @@ def migrate(migrator: Migrator, database: pw.Database, *, fake=False): migrator.add_constraint( "session", "session_last_used_created_at", - pw.Check("last_used IS NULL OR last_used <= created_at"), + pw.Check("last_used IS NULL OR last_used >= created_at"), ) diff --git a/backend/linkpulse/models.py b/backend/linkpulse/models.py index c9fc192..dc6b640 100644 --- a/backend/linkpulse/models.py +++ b/backend/linkpulse/models.py @@ -70,9 +70,12 @@ class Session(BaseModel): class Meta: constraints = [ - Check("LENGTH(token) = 32"), - Check("expiry > created_at"), - Check("last_used IS NULL OR last_used <= created_at"), + Check("LENGTH(token) = 32", name="session_token_length"), + Check("expiry > created_at", name="session_expiry_created_at"), + Check( + "last_used IS NULL OR last_used >= created_at", + name="session_last_used_created_at", + ), ] @property From d996d30d81370dc7c5dce1c83d06855e5ead456e Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 20:12:18 -0600 Subject: [PATCH 048/100] Add missing save() call, add TODOs, explicit default last_used to None Doesn't seem like explicitly setting the default to None does much here; automatic migration didn't trigger. --- backend/linkpulse/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/linkpulse/models.py b/backend/linkpulse/models.py index dc6b640..db4bb0e 100644 --- a/backend/linkpulse/models.py +++ b/backend/linkpulse/models.py @@ -50,6 +50,9 @@ class User(BaseModel): deleted_at = DateTimeField(null=True) deleted = flags.flag(1) + # TODO: delete method, ensure sessions are deleted as well + # TODO: undelete method + class Session(BaseModel): """ @@ -66,7 +69,7 @@ class Session(BaseModel): expiry = DateTimeField() created_at = DateTimeField(default=utc_now) - last_used = DateTimeField(null=True) + last_used = DateTimeField(default=None, null=True) class Meta: constraints = [ @@ -104,3 +107,6 @@ class Session(BaseModel): if now is None: now = utc_now() self.last_used = now # type: ignore + # TODO: This should be buffered, as it'll be called *constantly*, perhaps every single request. + # The ideal solution would be emitting updates to a Redis-based cache, and then flushing to the database every few seconds/minute. + self.save() From f9b12b8d0a1f931eccc0c81d5af43ff12d5f4b5e Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 20:13:34 -0600 Subject: [PATCH 049/100] Add validate_session() with constraint tests --- backend/linkpulse/routers/authentication.py | 24 ++++++++++++++++++ backend/linkpulse/tests/test_session.py | 28 +++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/backend/linkpulse/routers/authentication.py b/backend/linkpulse/routers/authentication.py index f0597cd..8f3e1f2 100644 --- a/backend/linkpulse/routers/authentication.py +++ b/backend/linkpulse/routers/authentication.py @@ -4,3 +4,27 @@ from fastapi import APIRouter from linkpulse.models import User, Session router = APIRouter() + + +def validate_session( + token: str, user: bool = True +) -> Tuple[bool, bool, Optional[User]]: + """ + Given a token, validate that the session exists and is not expired. + + This function has side effects: + - This function updates last_used if `user` is True. + - This function will invalidate the session if it is expired. + """ + # Check if session exists + session = Session.get_or_none(Session.token == token) + if session is None: + return False, False, None + + # Check if session is expired + if session.is_expired(revoke=True): + return True, False, None + + if user: + session.use() + return True, True, session.user diff --git a/backend/linkpulse/tests/test_session.py b/backend/linkpulse/tests/test_session.py index 6083686..b5d1e40 100644 --- a/backend/linkpulse/tests/test_session.py +++ b/backend/linkpulse/tests/test_session.py @@ -6,6 +6,9 @@ from linkpulse.models import Session from linkpulse.tests.random import random_string from linkpulse.tests.test_user import user from linkpulse.utilities import utc_now +from linkpulse.routers.authentication import validate_session + +from peewee import IntegrityError logger = structlog.get_logger() @@ -49,3 +52,28 @@ def test_expiry_valid(session): def test_expiry_invalid(expired_session): assert expired_session.is_expired() is True + + +def test_session_constraint_token_length(user): + with pytest.raises(IntegrityError): + Session.create( + user=user, token=random_string(31), expiry=utc_now() + timedelta(hours=1) + ) + Session.create( + user=user, token=random_string(32), expiry=utc_now() + timedelta(hours=1) + ) + + +def test_session_constraint_expiry(user): + with pytest.raises(IntegrityError): + Session.create(user=user, token=random_string(31), expiry=utc_now()) + Session.create( + user=user, token=random_string(32), expiry=utc_now() + timedelta(minutes=1) + ) + + +def test_validate_session(db, session): + assert session.last_used is None + assert validate_session(session.token, user=True) == (True, True, session.user) + session = Session.get(Session.token == session.token) + assert session.last_used is not None From 1075d05a431a40a94abac98f28fb0c5126fc1f0e Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 20:16:13 -0600 Subject: [PATCH 050/100] Remove problematic final coverage step No idea what's going wrong with this thing, but it's never worked properly --- .github/workflows/test.yaml | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 44b9c39..49b97d1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -77,19 +77,4 @@ jobs: uses: MishaKav/pytest-coverage-comment@main with: pytest-coverage-path: backend/pytest-coverage.txt - junitxml-path: backend/pytest.xml - - - name: Check the output coverage - run: | - echo "Coverage Percentage - ${{ steps.coverageComment.outputs.coverage }}" - echo "Coverage Color - ${{ steps.coverageComment.outputs.color }}" - echo "Summary Report - ${{ steps.coverageComment.outputs.summaryReport }}" - - echo "Coverage Warnings - ${{ steps.coverageComment.outputs.warnings }}" - - echo "Coverage Errors - ${{ steps.coverageComment.outputs.errors }}" - echo "Coverage Failures - ${{ steps.coverageComment.outputs.failures }}" - echo "Coverage Skipped - ${{ steps.coverageComment.outputs.skipped }}" - echo "Coverage Tests - ${{ steps.coverageComment.outputs.tests }}" - echo "Coverage Time - ${{ steps.coverageComment.outputs.time }}" - echo "Not Success Test Info - ${{ steps.coverageComment.outputs.notSuccessTestInfo }}" \ No newline at end of file + junitxml-path: backend/pytest.xml \ No newline at end of file From be4942c1d81af69ff155d77ee2ed91f38ea9e92b Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 20:22:18 -0600 Subject: [PATCH 051/100] Add misc router for health/migration routes, rename 'authentication' router to 'auth' --- backend/linkpulse/app.py | 26 +++--------------- .../routers/{authentication.py => auth.py} | 0 backend/linkpulse/routers/misc.py | 27 +++++++++++++++++++ backend/linkpulse/tests/test_session.py | 2 +- 4 files changed, 31 insertions(+), 24 deletions(-) rename backend/linkpulse/routers/{authentication.py => auth.py} (100%) create mode 100644 backend/linkpulse/routers/misc.py diff --git a/backend/linkpulse/app.py b/backend/linkpulse/app.py index 76b3d3d..bde4770 100644 --- a/backend/linkpulse/app.py +++ b/backend/linkpulse/app.py @@ -10,7 +10,6 @@ from fastapi import FastAPI from fastapi.responses import ORJSONResponse from fastapi_cache import FastAPICache from fastapi_cache.backends.inmemory import InMemoryBackend -from fastapi_cache.decorator import cache from linkpulse.logging import setup_logging from linkpulse.middleware import LoggingMiddleware from linkpulse.utilities import get_db, is_development @@ -45,10 +44,11 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]: db.close() -from linkpulse.routers import authentication +from linkpulse.routers import auth, misc app = FastAPI(lifespan=lifespan, default_response_class=ORJSONResponse) -app.include_router(authentication.router) +app.include_router(auth.router) +app.include_router(misc.router) setup_logging() @@ -70,23 +70,3 @@ if is_development: app.add_middleware(LoggingMiddleware) app.add_middleware(CorrelationIdMiddleware) - - -@app.get("/health") -async def health(): - # TODO: Check database connection - return "OK" - - -@app.get("/api/migration") -@cache(expire=60) -async def get_migration(): - """ - Returns the details of the most recent migration. - """ - # Kind of insecure, but this is just a demo thing to show that migratehistory is available. - cursor = db.execute_sql( - "SELECT name, migrated_at FROM migratehistory ORDER BY migrated_at DESC LIMIT 1" - ) - name, migrated_at = cursor.fetchone() - return {"name": name, "migrated_at": migrated_at} diff --git a/backend/linkpulse/routers/authentication.py b/backend/linkpulse/routers/auth.py similarity index 100% rename from backend/linkpulse/routers/authentication.py rename to backend/linkpulse/routers/auth.py diff --git a/backend/linkpulse/routers/misc.py b/backend/linkpulse/routers/misc.py new file mode 100644 index 0000000..5c5b5df --- /dev/null +++ b/backend/linkpulse/routers/misc.py @@ -0,0 +1,27 @@ +from fastapi import APIRouter +from fastapi_cache.decorator import cache +from linkpulse.utilities import get_db + +router = APIRouter() + +db = get_db() + + +@router.get("/health") +async def health(): + # TODO: Check database connection + return "OK" + + +@router.get("/api/migration") +@cache(expire=60) +async def get_migration(): + """ + Returns the details of the most recent migration. + """ + # Kind of insecure, but this is just a demo thing to show that migratehistory is available. + cursor = db.execute_sql( + "SELECT name, migrated_at FROM migratehistory ORDER BY migrated_at DESC LIMIT 1" + ) + name, migrated_at = cursor.fetchone() + return {"name": name, "migrated_at": migrated_at} diff --git a/backend/linkpulse/tests/test_session.py b/backend/linkpulse/tests/test_session.py index b5d1e40..7a246cf 100644 --- a/backend/linkpulse/tests/test_session.py +++ b/backend/linkpulse/tests/test_session.py @@ -6,7 +6,7 @@ from linkpulse.models import Session from linkpulse.tests.random import random_string from linkpulse.tests.test_user import user from linkpulse.utilities import utc_now -from linkpulse.routers.authentication import validate_session +from linkpulse.routers.auth import validate_session from peewee import IntegrityError From 8c111cf14eab1fc2aaa6a0b1207f02ccf72d9beb Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 20:39:27 -0600 Subject: [PATCH 052/100] Minor formatting concerns, remove dangling IPAddress usage --- .vscode/settings.json | 9 ++- backend/linkpulse/__main__.py | 17 +++--- .../004_create_user_remove_ipaddress.py | 8 +-- backend/linkpulse/routers/auth.py | 56 ++++++++++++++++++- backend/linkpulse/routers/misc.py | 14 ++++- 5 files changed, 86 insertions(+), 18 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 14f1520..5169b3a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,19 +4,26 @@ "backref", "bpython", "Callsite", + "clsx", "excepthook", "inmemory", "linkpulse", "migratehistory", "Nixpacks", "ORJSON", + "pext", + "pwdlib", + "pytest", "pytz", + "rtype", "starlette", "structlog", + "tailwindcss", "timestamper" ], "python.analysis.extraPaths": ["./backend/"], "[github-actions-workflow]": { "editor.formatOnSave": false - } + }, + "python.analysis.diagnosticMode": "workspace" } diff --git a/backend/linkpulse/__main__.py b/backend/linkpulse/__main__.py index 2d13d2b..45a6b38 100644 --- a/backend/linkpulse/__main__.py +++ b/backend/linkpulse/__main__.py @@ -16,17 +16,18 @@ setup_logging() import os import sys -import structlog +import structlog logger = structlog.get_logger() -def main(*args): - """ - Primary entrypoint for the LinkPulse application - - Don't import any modules globally unless you're certain it's necessary. Imports should be tightly controlled. - """ +def main(*args: str) -> None: + """Primary entrypoint for the LinkPulse application + NOTE: Don't import any modules globally unless you're certain it's necessary. Imports should be tightly controlled. + :param args: The command-line arguments to parse and execute. + :type args: str""" + if args[0] == "serve": from linkpulse.utilities import is_development from uvicorn import run @@ -57,9 +58,9 @@ def main(*args): # import most useful objects, models, and functions lp = linkpulse # alias - from linkpulse.utilities import get_db from linkpulse.app import app - from linkpulse.models import BaseModel, IPAddress + from linkpulse.models import BaseModel, User, Session + from linkpulse.utilities import get_db db = get_db() diff --git a/backend/linkpulse/migrations/004_create_user_remove_ipaddress.py b/backend/linkpulse/migrations/004_create_user_remove_ipaddress.py index 9322940..d85aa7a 100644 --- a/backend/linkpulse/migrations/004_create_user_remove_ipaddress.py +++ b/backend/linkpulse/migrations/004_create_user_remove_ipaddress.py @@ -36,7 +36,7 @@ with suppress(ImportError): def migrate(migrator: Migrator, database: pw.Database, *, fake=False): """Write your migrations here.""" - + @migrator.create_model class User(pw.Model): id = pw.AutoField() @@ -48,12 +48,12 @@ def migrate(migrator: Migrator, database: pw.Database, *, fake=False): class Meta: table_name = "user" - migrator.remove_model('ipaddress') + migrator.remove_model("ipaddress") def rollback(migrator: Migrator, database: pw.Database, *, fake=False): """Write your rollback migrations here.""" - + @migrator.create_model class IPAddress(pw.Model): ip = pw.CharField(max_length=255, primary_key=True) @@ -63,4 +63,4 @@ def rollback(migrator: Migrator, database: pw.Database, *, fake=False): class Meta: table_name = "ipaddress" - migrator.remove_model('user') + migrator.remove_model("user") diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index 8f3e1f2..435aea5 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -9,12 +9,21 @@ router = APIRouter() def validate_session( token: str, user: bool = True ) -> Tuple[bool, bool, Optional[User]]: - """ - Given a token, validate that the session exists and is not expired. + """Given a token, validate that the session exists and is not expired. This function has side effects: - This function updates last_used if `user` is True. - This function will invalidate the session if it is expired. + + :param token: The session token to validate. + :type token: str + :param user: Whether to update the last_used timestamp of the session. + :type user: bool + :return: A tuple containing: + - A boolean indicating if the session exists. + - A boolean indicating if the session is valid. + - The User object if the session is valid, otherwise None. + :rtype: Tuple[bool, bool, Optional[User]] """ # Check if session exists session = Session.get_or_none(Session.token == token) @@ -28,3 +37,46 @@ def validate_session( if user: session.use() return True, True, session.user + + +@router.post("/api/login") +async def login(): + # Validate parameters + # Hash regardless of user existence to prevent timing attacks + # Check if user exists, return 401 if not + # Check if password matches, return 401 if not + # Create session + # Set Cookie of session token + # Return 200 with mild user information + pass + + +@router.post("/api/logout") +async def logout(): + # TODO: Force logout parameter, logout ALL sessions for User + # Get session token from Cookie + # Delete session + # Return 200 + pass + + +@router.post("/api/register") +async def register(): + # Validate parameters + # Hash password + # Create User + # Create Session + # Set Cookie of session token + # Return 200 with mild user information + pass + + +@router.get("/api/sessions") +async def sessions(): + pass + + +# GET /api/user/{id}/sessions +# GET /api/user/{id}/sessions/{token} +# DELETE /api/user/{id}/sessions +# POST /api/user/{id}/logout (delete all sessions) diff --git a/backend/linkpulse/routers/misc.py b/backend/linkpulse/routers/misc.py index 5c5b5df..e91c41e 100644 --- a/backend/linkpulse/routers/misc.py +++ b/backend/linkpulse/routers/misc.py @@ -1,3 +1,7 @@ +"""Miscellaneous endpoints for the Linkpulse API.""" + +from typing import Any + from fastapi import APIRouter from fastapi_cache.decorator import cache from linkpulse.utilities import get_db @@ -9,15 +13,19 @@ db = get_db() @router.get("/health") async def health(): + """An endpoint to check if the service is running. + :return: OK + :rtype: Literal['OK']""" # TODO: Check database connection return "OK" @router.get("/api/migration") @cache(expire=60) -async def get_migration(): - """ - Returns the details of the most recent migration. +async def get_migration() -> dict[str, Any]: + """Get the last migration name and timestamp from the migratehistory table. + :return: The last migration name and timestamp. + :rtype: dict[str, Any] """ # Kind of insecure, but this is just a demo thing to show that migratehistory is available. cursor = db.execute_sql( From 93a6dd5972a04f89a86c8366beefab5dfb455ba7 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 20:46:29 -0600 Subject: [PATCH 053/100] workspace diagnostic mode, cSpell ignorePaths --- .vscode/settings.json | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5169b3a..1d90d09 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,24 +6,46 @@ "Callsite", "clsx", "excepthook", + "humanfs", + "humanwhocodes", "inmemory", + "jridgewell", "linkpulse", "migratehistory", "Nixpacks", + "nkzw", "ORJSON", "pext", + "postcss", "pwdlib", + "pyproject", "pytest", "pytz", + "riscv", + "rollup", "rtype", "starlette", "structlog", + "sugarss", "tailwindcss", - "timestamper" + "timestamper", + "vitest" ], "python.analysis.extraPaths": ["./backend/"], "[github-actions-workflow]": { "editor.formatOnSave": false }, - "python.analysis.diagnosticMode": "workspace" + "python.analysis.diagnosticMode": "workspace", + "cSpell.ignorePaths": [ + "package-lock.json", + "node_modules", + "vscode-extension", + ".git/objects", + ".vscode", + ".vscode-insiders", + "settings.json", + "*.lock", + "*lock.*", + "package.json" + ] } From 126d6b4757331e1d5c12ee9dab7a7e4b1214a2e0 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 21:30:09 -0600 Subject: [PATCH 054/100] move migration squashing suggestion limit to 15 --- backend/linkpulse/migrate.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/linkpulse/migrate.py b/backend/linkpulse/migrate.py index 0d94bdb..e104eda 100644 --- a/backend/linkpulse/migrate.py +++ b/backend/linkpulse/migrate.py @@ -178,9 +178,10 @@ def main(*args: str) -> None: else: logger.info("No database changes detected.") - if len(current) > 5: + migration_limit: int = 15 + if len(current) > migration_limit: if questionary.confirm( - "There are more than 5 migrations applied. Do you want to merge them?", + f"There are more than {migration_limit} migrations applied. Do you want to merge them?", default=False, ).ask(): logger.info("Merging migrations...") From 15ce80bb662a605686bdcfa57265dcb19acfa7c8 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 21:30:46 -0600 Subject: [PATCH 055/100] migration: User.password_hash length to 97, Session.token index off by one hell fml also why's the index only applying now? what'd I do lol --- ..._hash_length_adjust_session_token_index.py | 50 +++++++++++++++++++ backend/linkpulse/models.py | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 backend/linkpulse/migrations/007_password_hash_length_adjust_session_token_index.py diff --git a/backend/linkpulse/migrations/007_password_hash_length_adjust_session_token_index.py b/backend/linkpulse/migrations/007_password_hash_length_adjust_session_token_index.py new file mode 100644 index 0000000..d656f83 --- /dev/null +++ b/backend/linkpulse/migrations/007_password_hash_length_adjust_session_token_index.py @@ -0,0 +1,50 @@ +"""Peewee migrations -- 007_password_hash_length_adjust_session_token_index.py. + +Some examples (model - class or model name):: + + > Model = migrator.orm['table_name'] # Return model in current state by name + > Model = migrator.ModelClass # Return model in current state by name + + > migrator.sql(sql) # Run custom SQL + > migrator.run(func, *args, **kwargs) # Run python function with the given args + > migrator.create_model(Model) # Create a model (could be used as decorator) + > migrator.remove_model(model, cascade=True) # Remove a model + > migrator.add_fields(model, **fields) # Add fields to a model + > migrator.change_fields(model, **fields) # Change fields + > migrator.remove_fields(model, *field_names, cascade=True) + > migrator.rename_field(model, old_field_name, new_field_name) + > migrator.rename_table(model, new_table_name) + > migrator.add_index(model, *col_names, unique=False) + > migrator.add_not_null(model, *field_names) + > migrator.add_default(model, field_name, default) + > migrator.add_constraint(model, name, sql) + > migrator.drop_index(model, *col_names) + > migrator.drop_not_null(model, *field_names) + > migrator.drop_constraints(model, *constraints) + +""" + +from contextlib import suppress + +import peewee as pw +from peewee_migrate import Migrator + + +with suppress(ImportError): + import playhouse.postgres_ext as pw_pext + + +def migrate(migrator: Migrator, database: pw.Database, *, fake=False): + """Write your migrations here.""" + + migrator.change_fields('user', password_hash=pw.CharField(max_length=97)) + + migrator.add_index('session', 'token', unique=True) + + +def rollback(migrator: Migrator, database: pw.Database, *, fake=False): + """Write your rollback migrations here.""" + + migrator.drop_index('session', 'token') + + migrator.change_fields('user', password_hash=pw.CharField(max_length=96)) diff --git a/backend/linkpulse/models.py b/backend/linkpulse/models.py index db4bb0e..751e5bc 100644 --- a/backend/linkpulse/models.py +++ b/backend/linkpulse/models.py @@ -43,7 +43,7 @@ class User(BaseModel): email = CharField(unique=True, max_length=45) flags = BitField() # full hash with encoded salt/parameters, argon2 but assume nothing - password_hash = CharField(max_length=96) + password_hash = CharField(max_length=97) created_at = DateTimeField(default=utc_now) updated_at = DateTimeField(default=utc_now) # prefer soft deletes before hard deletes From 47dc26f842cf0b791ace8dbeb1d196f056326b76 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 21:31:13 -0600 Subject: [PATCH 056/100] add email-validator package --- backend/poetry.lock | 37 ++++++++++++++++++++++++++++++++++++- backend/pyproject.toml | 1 + 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/backend/poetry.lock b/backend/poetry.lock index 8687fda..e86c8a5 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -549,6 +549,41 @@ files = [ {file = "cwcwidth-0.1.9.tar.gz", hash = "sha256:f19d11a0148d4a8cacd064c96e93bca8ce3415a186ae8204038f45e108db76b8"}, ] +[[package]] +name = "dnspython" +version = "2.7.0" +description = "DNS toolkit" +optional = false +python-versions = ">=3.9" +files = [ + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=43)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=1.0.0)"] +idna = ["idna (>=3.7)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + +[[package]] +name = "email-validator" +version = "2.2.0" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + [[package]] name = "execnet" version = "2.1.1" @@ -1661,4 +1696,4 @@ h11 = ">=0.9.0,<1" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "bf1c24a8867c448db911c50be70cfdf48df6b87ea39f10f66a0e34f8139f6d82" +content-hash = "cb3c6f5fdc7f362a6fd46c1f719e68280705ef23956ebc020475d985a844247f" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 68f9c78..8b40f57 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -30,6 +30,7 @@ orjson = "^3.10.10" hypercorn = "^0.17.3" pwdlib = {extras = ["argon2"], version = "^0.2.1"} pytest-xdist = "^3.6.1" +email-validator = "^2.2.0" [tool.poetry.group.dev.dependencies] From a4c8336c1a6ee302bc7aadb9b0dc737792b995a7 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 22:29:55 -0600 Subject: [PATCH 057/100] Add limits package, RateLimiter dependency, test_depends, begin /api/login route --- backend/linkpulse/dependencies.py | 35 ++++++ backend/linkpulse/routers/auth.py | 35 +++++- backend/linkpulse/tests/test_depends.py | 21 ++++ backend/poetry.lock | 146 +++++++++++++++++++++++- backend/pyproject.toml | 1 + 5 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 backend/linkpulse/dependencies.py create mode 100644 backend/linkpulse/tests/test_depends.py diff --git a/backend/linkpulse/dependencies.py b/backend/linkpulse/dependencies.py new file mode 100644 index 0000000..514b48b --- /dev/null +++ b/backend/linkpulse/dependencies.py @@ -0,0 +1,35 @@ +import structlog +from fastapi import HTTPException, Request, Response, status +from limits.strategies import MovingWindowRateLimiter +from limits.storage import MemoryStorage +from limits import parse + +storage = MemoryStorage() +strategy = MovingWindowRateLimiter(storage) + +logger = structlog.get_logger() + + +class RateLimiter: + def __init__(self, limit: str): + self.limit = parse(limit) + self.retry_after = str(self.limit.get_expiry()) + + async def __call__(self, request: Request, response: Response): + logger.debug("Rate limiting request", path=request.url.path) + + key = request.headers.get("X-Real-IP") + if key is None: + if request.client is None: + logger.warning("No client information available for request.") + return False + key = request.client.host + + if not strategy.hit(self.limit, key): + logger.warning("Rate limit exceeded", key=key) + raise HTTPException( + status_code=status.HTTP_429_TOO_MANY_REQUESTS, + detail="Too Many Requests", + headers={"Retry-After": self.retry_after}, + ) + return True diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index 435aea5..6541985 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -1,10 +1,17 @@ from typing import Tuple, Optional -from fastapi import APIRouter +from pwdlib import PasswordHash +from pwdlib.hashers.argon2 import Argon2Hasher +from fastapi import APIRouter, Depends +from pydantic import BaseModel, EmailStr, Field +from linkpulse.dependencies import RateLimiter from linkpulse.models import User, Session router = APIRouter() +hasher = PasswordHash([Argon2Hasher()]) +dummy_hash = "$argon2id$v=19$m=65536,t=3,p=4$Ii3hm5/NqcJddQDFK24Wtw$I99xV/qkaLROo0VZcvaZrYMAD9RTcWzxY5/RbMoRLQ4" + def validate_session( token: str, user: bool = True @@ -39,10 +46,28 @@ def validate_session( return True, True, session.user -@router.post("/api/login") -async def login(): - # Validate parameters - # Hash regardless of user existence to prevent timing attacks +class LoginBody(BaseModel): + email: EmailStr + password: str = Field(min_length=1) + remember_me: bool = False + + +class LoginError(BaseModel): + error: str + + +@router.post("/api/login", dependencies=[Depends(RateLimiter("6/minute"))]) +async def login(body: LoginBody): + # Acquire user by email + user = User.get_or_none(User.email == body.email) + + if user is None: + # Hash regardless of user existence to prevent timing attacks + hasher.verify(body.password, dummy_hash) + return LoginError(error="Invalid email or password") + + # valid, updated_hash = hasher.verify_and_update(body.password, existing_hash) + # Check if user exists, return 401 if not # Check if password matches, return 401 if not # Create session diff --git a/backend/linkpulse/tests/test_depends.py b/backend/linkpulse/tests/test_depends.py new file mode 100644 index 0000000..02a09d3 --- /dev/null +++ b/backend/linkpulse/tests/test_depends.py @@ -0,0 +1,21 @@ +import structlog +from fastapi import status +from fastapi.testclient import TestClient +from linkpulse.app import app + +logger = structlog.get_logger() + + +def test_rate_limit(): + args = {"email": "test@test.com", "password": "test"} + + with TestClient(app) as client: + for _ in range(6): + response = client.post("/api/login", json=args) + assert response.status_code == status.HTTP_200_OK + + # 7th request should be rate limited + response = client.post("/api/login", json=args) + assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS + assert "Retry-After" in response.headers + assert int(response.headers["Retry-After"]) > 1 diff --git a/backend/poetry.lock b/backend/poetry.lock index e86c8a5..d5cc6ed 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -549,6 +549,23 @@ files = [ {file = "cwcwidth-0.1.9.tar.gz", hash = "sha256:f19d11a0148d4a8cacd064c96e93bca8ce3415a186ae8204038f45e108db76b8"}, ] +[[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + [[package]] name = "dnspython" version = "2.7.0" @@ -868,6 +885,25 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "importlib-resources" +version = "6.4.5" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, + {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -893,6 +929,35 @@ files = [ [package.dependencies] ansicon = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "limits" +version = "3.13.0" +description = "Rate limiting utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "limits-3.13.0-py3-none-any.whl", hash = "sha256:9767f7233da4255e9904b79908a728e8ec0984c0b086058b4cbbd309aea553f6"}, + {file = "limits-3.13.0.tar.gz", hash = "sha256:6571b0c567bfa175a35fed9f8a954c0c92f1c3200804282f1b8f1de4ad98a953"}, +] + +[package.dependencies] +deprecated = ">=1.2" +importlib-resources = ">=1.3" +packaging = ">=21,<25" +typing-extensions = "*" + +[package.extras] +all = ["aetcd", "coredis (>=3.4.0,<5)", "emcache (>=0.6.1)", "emcache (>=1)", "etcd3", "motor (>=3,<4)", "pymemcache (>3,<5.0.0)", "pymongo (>4.1,<5)", "redis (>3,!=4.5.2,!=4.5.3,<6.0.0)", "redis (>=4.2.0,!=4.5.2,!=4.5.3)"] +async-etcd = ["aetcd"] +async-memcached = ["emcache (>=0.6.1)", "emcache (>=1)"] +async-mongodb = ["motor (>=3,<4)"] +async-redis = ["coredis (>=3.4.0,<5)"] +etcd = ["etcd3"] +memcached = ["pymemcache (>3,<5.0.0)"] +mongodb = ["pymongo (>4.1,<5)"] +redis = ["redis (>3,!=4.5.2,!=4.5.3,<6.0.0)"] +rediscluster = ["redis (>=4.2.0,!=4.5.2,!=4.5.3)"] + [[package]] name = "memory-profiler" version = "0.61.0" @@ -1679,6 +1744,85 @@ files = [ {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + [[package]] name = "wsproto" version = "1.2.0" @@ -1696,4 +1840,4 @@ h11 = ">=0.9.0,<1" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "cb3c6f5fdc7f362a6fd46c1f719e68280705ef23956ebc020475d985a844247f" +content-hash = "9dff2a47bb95e65f15616bb82926eb81d418456c5ec60db46dfe06056c643e31" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 8b40f57..a886a0b 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -31,6 +31,7 @@ hypercorn = "^0.17.3" pwdlib = {extras = ["argon2"], version = "^0.2.1"} pytest-xdist = "^3.6.1" email-validator = "^2.2.0" +limits = "^3.13.0" [tool.poetry.group.dev.dependencies] From ee4f3c64d9a85e965095f7be43a59a2f58668c80 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 22:44:57 -0600 Subject: [PATCH 058/100] Switch test_user user fixture to proper hasher --- backend/linkpulse/tests/test_user.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/linkpulse/tests/test_user.py b/backend/linkpulse/tests/test_user.py index bd0f37c..e440579 100644 --- a/backend/linkpulse/tests/test_user.py +++ b/backend/linkpulse/tests/test_user.py @@ -1,10 +1,14 @@ import pytest +import structlog from linkpulse.models import User -from linkpulse.tests.random import epoch, random_email, random_string +from linkpulse.routers.auth import hasher +from linkpulse.tests.random import random_email, random_string + +logger = structlog.get_logger() @pytest.fixture def user(): return User.create( - email=random_email(), password_hash=str(epoch()) + random_string(64) + email=random_email(), password_hash=hasher.hash(random_string(64)) ) From fa80bd0649757c578df5d8fa856ceca5098a0463 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 22:47:17 -0600 Subject: [PATCH 059/100] Use xdist auto in pytest workflow --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 49b97d1..c12767b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -64,7 +64,7 @@ jobs: run: | cd backend set -o pipefail # otherwise 'tee' will eat the exit code - poetry run pytest --color=yes --cov=linkpulse --cov-report=term-missing:skip-covered --junitxml=pytest.xml | tee pytest-coverage.txt + poetry run pytest -n auto --color=yes --cov=linkpulse --cov-report=term-missing:skip-covered --junitxml=pytest.xml | tee pytest-coverage.txt # pytest-coverage-comment won't error if the files are missing if [ ! -f ./pytest-coverage.txt ] || [ ! -f ./pytest.xml ]; then From 058be9602ab167255177c021df35faa97bacde22 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 22:52:25 -0600 Subject: [PATCH 060/100] Use nproc to specify xdist workers --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c12767b..f22100b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -64,7 +64,7 @@ jobs: run: | cd backend set -o pipefail # otherwise 'tee' will eat the exit code - poetry run pytest -n auto --color=yes --cov=linkpulse --cov-report=term-missing:skip-covered --junitxml=pytest.xml | tee pytest-coverage.txt + poetry run pytest -n $(nproc) --color=yes --cov=linkpulse --cov-report=term-missing:skip-covered --junitxml=pytest.xml | tee pytest-coverage.txt # pytest-coverage-comment won't error if the files are missing if [ ! -f ./pytest-coverage.txt ] || [ ! -f ./pytest.xml ]; then From cd900288ff4602c471e0a86ed99d2055259149d4 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 23:23:21 -0600 Subject: [PATCH 061/100] Extend black line-length to 110 --- backend/pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index a886a0b..b3d723e 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -45,3 +45,6 @@ pytest-cov = "^6.0.0" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + +[tool.black] +line-length = 110 \ No newline at end of file From 4c2c4bc2adc8ae7dca6ee60c018cee5553b03018 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 23:23:55 -0600 Subject: [PATCH 062/100] Add hacky fix for rate-limiter segregation between pytests --- backend/linkpulse/dependencies.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/linkpulse/dependencies.py b/backend/linkpulse/dependencies.py index 514b48b..1df57c5 100644 --- a/backend/linkpulse/dependencies.py +++ b/backend/linkpulse/dependencies.py @@ -1,3 +1,4 @@ +import os import structlog from fastapi import HTTPException, Request, Response, status from limits.strategies import MovingWindowRateLimiter @@ -8,6 +9,7 @@ storage = MemoryStorage() strategy = MovingWindowRateLimiter(storage) logger = structlog.get_logger() +is_pytest = os.environ.get("PYTEST_VERSION") is not None class RateLimiter: @@ -16,15 +18,19 @@ class RateLimiter: self.retry_after = str(self.limit.get_expiry()) async def __call__(self, request: Request, response: Response): - logger.debug("Rate limiting request", path=request.url.path) - key = request.headers.get("X-Real-IP") + if key is None: if request.client is None: logger.warning("No client information available for request.") return False key = request.client.host + if is_pytest: + # This is somewhat hacky, I'm not sure if there's a way it can break during pytesting, but look here if odd rate limiting errors occur during tests + # The reason for this is so tests don't compete with each other for rate limiting + key += "." + os.environ["PYTEST_CURRENT_TEST"] + if not strategy.hit(self.limit, key): logger.warning("Rate limit exceeded", key=key) raise HTTPException( From fd90873f7b18442ea83cbb27b600be310d5ae2dc Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 23:24:30 -0600 Subject: [PATCH 063/100] Add test_auth_login --- backend/linkpulse/routers/auth.py | 7 ++++++- backend/linkpulse/tests/test_auth.py | 19 +++++++++++++++++++ backend/linkpulse/tests/test_user.py | 4 +--- 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 backend/linkpulse/tests/test_auth.py diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index 6541985..13c4bbb 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -1,5 +1,7 @@ from typing import Tuple, Optional +from fastapi import status +from fastapi.responses import ORJSONResponse from pwdlib import PasswordHash from pwdlib.hashers.argon2 import Argon2Hasher from fastapi import APIRouter, Depends @@ -64,7 +66,10 @@ async def login(body: LoginBody): if user is None: # Hash regardless of user existence to prevent timing attacks hasher.verify(body.password, dummy_hash) - return LoginError(error="Invalid email or password") + return ORJSONResponse( + status_code=status.HTTP_401_UNAUTHORIZED, + content=LoginError(error="Invalid email or password"), + ) # valid, updated_hash = hasher.verify_and_update(body.password, existing_hash) diff --git a/backend/linkpulse/tests/test_auth.py b/backend/linkpulse/tests/test_auth.py new file mode 100644 index 0000000..93c9d67 --- /dev/null +++ b/backend/linkpulse/tests/test_auth.py @@ -0,0 +1,19 @@ +from fastapi import status +from fastapi.testclient import TestClient +from linkpulse.app import app +from linkpulse.tests.test_user import user + + +def test_auth_login(user): + args = {"email": "test@test.com", "password": "test"} + + with TestClient(app) as client: + response = client.post("/api/login", json=args) + assert response.status_code == status.HTTP_200_OK + # assert response.json()["token"] is not None + + response = client.post("/api/login", json={**args, "email": "invalid_email"}) + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + + response = client.post("/api/login", json={**args, "password": "invalid_password"}) + assert response.status_code == status.HTTP_401_UNAUTHORIZED diff --git a/backend/linkpulse/tests/test_user.py b/backend/linkpulse/tests/test_user.py index e440579..4c30d2c 100644 --- a/backend/linkpulse/tests/test_user.py +++ b/backend/linkpulse/tests/test_user.py @@ -9,6 +9,4 @@ logger = structlog.get_logger() @pytest.fixture def user(): - return User.create( - email=random_email(), password_hash=hasher.hash(random_string(64)) - ) + return User.create(email=random_email(), password_hash=hasher.hash("password")) From 10919d03330c2530cf97e01d61298ade4c1b1050 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 23:27:27 -0600 Subject: [PATCH 064/100] Switch limits to asynchronous storage/strategy --- backend/linkpulse/dependencies.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/linkpulse/dependencies.py b/backend/linkpulse/dependencies.py index 1df57c5..db24be2 100644 --- a/backend/linkpulse/dependencies.py +++ b/backend/linkpulse/dependencies.py @@ -1,8 +1,8 @@ import os import structlog from fastapi import HTTPException, Request, Response, status -from limits.strategies import MovingWindowRateLimiter -from limits.storage import MemoryStorage +from limits.aio.strategies import MovingWindowRateLimiter +from limits.aio.storage import MemoryStorage from limits import parse storage = MemoryStorage() @@ -31,7 +31,7 @@ class RateLimiter: # The reason for this is so tests don't compete with each other for rate limiting key += "." + os.environ["PYTEST_CURRENT_TEST"] - if not strategy.hit(self.limit, key): + if not await strategy.hit(self.limit, key): logger.warning("Rate limit exceeded", key=key) raise HTTPException( status_code=status.HTTP_429_TOO_MANY_REQUESTS, From cb8dd80f33f2e102e5a38bc08ce32fde3f076275 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 00:16:37 -0600 Subject: [PATCH 065/100] finish login function, true hash of user fixture --- backend/linkpulse/models.py | 20 +++---- backend/linkpulse/routers/auth.py | 76 ++++++++++++++++++------- backend/linkpulse/tests/test_auth.py | 3 +- backend/linkpulse/tests/test_depends.py | 5 +- backend/linkpulse/tests/test_user.py | 2 +- 5 files changed, 67 insertions(+), 39 deletions(-) diff --git a/backend/linkpulse/models.py b/backend/linkpulse/models.py index 751e5bc..57b2725 100644 --- a/backend/linkpulse/models.py +++ b/backend/linkpulse/models.py @@ -4,20 +4,13 @@ It also provides a base model with database connection details. """ import datetime +import secrets from os import getenv from typing import Optional import structlog from linkpulse.utilities import utc_now -from peewee import ( - AutoField, - BitField, - CharField, - Check, - DateTimeField, - ForeignKeyField, - Model, -) +from peewee import AutoField, BitField, CharField, Check, DateTimeField, ForeignKeyField, Model from playhouse.db_url import connect logger = structlog.get_logger() @@ -81,13 +74,16 @@ class Session(BaseModel): ), ] + @classmethod + def generate_token(cls) -> str: + alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + return "".join(secrets.choice(alphabet) for _ in range(32)) + @property def expiry_utc(self) -> datetime.datetime: return self.expiry.replace(tzinfo=datetime.timezone.utc) # type: ignore - def is_expired( - self, revoke: bool = True, now: Optional[datetime.datetime] = None - ) -> bool: + def is_expired(self, revoke: bool = True, now: Optional[datetime.datetime] = None) -> bool: """ Check if the session is expired. If `revoke` is True, the session will be automatically revoked if it is expired. """ diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index 13c4bbb..c66f340 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -1,23 +1,32 @@ -from typing import Tuple, Optional +from datetime import datetime, timedelta +from math import e +from typing import Optional, Tuple -from fastapi import status -from fastapi.responses import ORJSONResponse +import structlog +from fastapi import APIRouter, Depends, Response, status +from fastapi.responses import JSONResponse +from linkpulse.dependencies import RateLimiter +from linkpulse.models import Session, User +from linkpulse.utilities import utc_now from pwdlib import PasswordHash from pwdlib.hashers.argon2 import Argon2Hasher -from fastapi import APIRouter, Depends from pydantic import BaseModel, EmailStr, Field -from linkpulse.dependencies import RateLimiter -from linkpulse.models import User, Session + +logger = structlog.get_logger() router = APIRouter() hasher = PasswordHash([Argon2Hasher()]) -dummy_hash = "$argon2id$v=19$m=65536,t=3,p=4$Ii3hm5/NqcJddQDFK24Wtw$I99xV/qkaLROo0VZcvaZrYMAD9RTcWzxY5/RbMoRLQ4" +dummy_hash = ( + "$argon2id$v=19$m=65536,t=3,p=4$Ii3hm5/NqcJddQDFK24Wtw$I99xV/qkaLROo0VZcvaZrYMAD9RTcWzxY5/RbMoRLQ4" +) + +# Session expiry times +default_session_expiry = timedelta(hours=12) +remember_me_session_expiry = timedelta(days=14) -def validate_session( - token: str, user: bool = True -) -> Tuple[bool, bool, Optional[User]]: +def validate_session(token: str, user: bool = True) -> Tuple[bool, bool, Optional[User]]: """Given a token, validate that the session exists and is not expired. This function has side effects: @@ -49,8 +58,8 @@ def validate_session( class LoginBody(BaseModel): - email: EmailStr - password: str = Field(min_length=1) + email: EmailStr # May be a heavy check; profiling could determine if this is necessary + password: str = Field(min_length=1) # Basic check, registration will have more stringent requirements remember_me: bool = False @@ -58,27 +67,50 @@ class LoginError(BaseModel): error: str -@router.post("/api/login", dependencies=[Depends(RateLimiter("6/minute"))]) -async def login(body: LoginBody): +class LoginSuccess(BaseModel): + email: EmailStr + expiry: datetime + + +@router.post( + "/api/login", + responses={200: {"model": LoginSuccess}, 401: {"model": LoginError}}, + dependencies=[Depends(RateLimiter("6/minute"))], +) +async def login(body: LoginBody, response: Response): # Acquire user by email user = User.get_or_none(User.email == body.email) if user is None: # Hash regardless of user existence to prevent timing attacks hasher.verify(body.password, dummy_hash) - return ORJSONResponse( - status_code=status.HTTP_401_UNAUTHORIZED, - content=LoginError(error="Invalid email or password"), - ) + response.status_code = status.HTTP_401_UNAUTHORIZED + return LoginError(error="Invalid email or password") - # valid, updated_hash = hasher.verify_and_update(body.password, existing_hash) + logger.warning("Hash", hash=user.password_hash) + valid, updated_hash = hasher.verify_and_update(body.password, user.password_hash) - # Check if user exists, return 401 if not # Check if password matches, return 401 if not + if not valid: + response.status_code = status.HTTP_401_UNAUTHORIZED + return LoginError(error="Invalid email or password") + + # Update password hash if necessary + if updated_hash: + user.password_hash = updated_hash + user.save() + # Create session + token = Session.generate_token() + session = Session.create( + token=token, + user=user, + expiry=utc_now() + (remember_me_session_expiry if body.remember_me else default_session_expiry), + ) + # Set Cookie of session token - # Return 200 with mild user information - pass + response.set_cookie("session", token, samesite="strict") + return {"email": user.email, "expiry": session.expiry} @router.post("/api/logout") diff --git a/backend/linkpulse/tests/test_auth.py b/backend/linkpulse/tests/test_auth.py index 93c9d67..f7c898f 100644 --- a/backend/linkpulse/tests/test_auth.py +++ b/backend/linkpulse/tests/test_auth.py @@ -5,12 +5,11 @@ from linkpulse.tests.test_user import user def test_auth_login(user): - args = {"email": "test@test.com", "password": "test"} + args = {"email": user.email, "password": "password"} with TestClient(app) as client: response = client.post("/api/login", json=args) assert response.status_code == status.HTTP_200_OK - # assert response.json()["token"] is not None response = client.post("/api/login", json={**args, "email": "invalid_email"}) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY diff --git a/backend/linkpulse/tests/test_depends.py b/backend/linkpulse/tests/test_depends.py index 02a09d3..eafc13f 100644 --- a/backend/linkpulse/tests/test_depends.py +++ b/backend/linkpulse/tests/test_depends.py @@ -2,12 +2,13 @@ import structlog from fastapi import status from fastapi.testclient import TestClient from linkpulse.app import app +from linkpulse.tests.test_user import user logger = structlog.get_logger() -def test_rate_limit(): - args = {"email": "test@test.com", "password": "test"} +def test_rate_limit(user): + args = {"email": user.email, "password": "password"} with TestClient(app) as client: for _ in range(6): diff --git a/backend/linkpulse/tests/test_user.py b/backend/linkpulse/tests/test_user.py index 4c30d2c..4a0fe0c 100644 --- a/backend/linkpulse/tests/test_user.py +++ b/backend/linkpulse/tests/test_user.py @@ -2,7 +2,7 @@ import pytest import structlog from linkpulse.models import User from linkpulse.routers.auth import hasher -from linkpulse.tests.random import random_email, random_string +from linkpulse.tests.random import random_email logger = structlog.get_logger() From 55eb864f77c761dda0283c0875d6672dedece39b Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 00:31:52 -0600 Subject: [PATCH 066/100] fixup test_auth_login, add expiry assertions --- backend/linkpulse/tests/test_auth.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/backend/linkpulse/tests/test_auth.py b/backend/linkpulse/tests/test_auth.py index f7c898f..fba3b15 100644 --- a/backend/linkpulse/tests/test_auth.py +++ b/backend/linkpulse/tests/test_auth.py @@ -1,18 +1,44 @@ +from datetime import datetime, timedelta +import structlog from fastapi import status from fastapi.testclient import TestClient from linkpulse.app import app from linkpulse.tests.test_user import user +from linkpulse.utilities import utc_now + +import pytest + +logger = structlog.get_logger() def test_auth_login(user): args = {"email": user.email, "password": "password"} with TestClient(app) as client: + + def test_expiry(response, expected): + expiry = datetime.fromisoformat(response.json()["expiry"]) + relative_expiry_days = (expiry - utc_now()).total_seconds() / timedelta(days=1).total_seconds() + assert relative_expiry_days == pytest.approx(expected, rel=1e-5) + + # Remember Me, default False response = client.post("/api/login", json=args) assert response.status_code == status.HTTP_200_OK + test_expiry(response, 0.5) + # Remember Me, True + response = client.post("/api/login", json={**args, "remember_me": True}) + assert response.status_code == status.HTTP_200_OK + test_expiry(response, 14) + + # Invalid Email response = client.post("/api/login", json={**args, "email": "invalid_email"}) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - response = client.post("/api/login", json={**args, "password": "invalid_password"}) + # Wrong Email + response = client.post("/api/login", json={**args, "email": "bad@email.com"}) + assert response.status_code == status.HTTP_401_UNAUTHORIZED + + # Wrong Password + response = client.post("/api/login", json={**args, "password": "bad_password"}) assert response.status_code == status.HTTP_401_UNAUTHORIZED From 32645a588af27964b108d2d479e20a9747269f8a Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 00:38:34 -0600 Subject: [PATCH 067/100] Update CHANGELOG.md --- CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e402826..8185777 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Added - A release checklist to the `CHANGELOG.md` file, as a reminder for procedure. -- An action workflow for invoking `pytest` -- Pytest coverage report generation in the CI/CD pipeline +- An action workflow for invoking `pytest`, with coverage report generation in CI/CD +- backend: Rate Limiting via `limits`, custom `RateLimiter` dependency +- backend: `Session` model constraints for `token` length, `expiry` & `last_used` timestamps - backend: `pwdlib[argon2]` for password hashing - backend: provided `LOG_JSON_FORMAT` and `LOG_LEVEL` environment variable defaults in `run.sh` development script - backend: `User` model, `Session` model with migration script @@ -21,6 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Changed +- Set `black` formatter line length to 120 characters +- backend: migration squashing threshold to 15 +- backend: moved top level `app` routes to `router.misc` - frontend: Updated `eslint` to `9.x.x`, `@types/node` to `22.9.x` ## Removed From 00a36430795edd577c93f98cccc6dac35dfa5f6c Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 12:13:56 -0600 Subject: [PATCH 068/100] Add proper cookie expiry time to Login route --- backend/linkpulse/routers/auth.py | 5 +++-- backend/linkpulse/tests/test_auth.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index c66f340..8ab4e61 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -102,14 +102,15 @@ async def login(body: LoginBody, response: Response): # Create session token = Session.generate_token() + session_duration = remember_me_session_expiry if body.remember_me else default_session_expiry session = Session.create( token=token, user=user, - expiry=utc_now() + (remember_me_session_expiry if body.remember_me else default_session_expiry), + expiry=utc_now() + session_duration, ) # Set Cookie of session token - response.set_cookie("session", token, samesite="strict") + response.set_cookie("session", token, samesite="strict", max_age=int(session_duration.total_seconds())) return {"email": user.email, "expiry": session.expiry} diff --git a/backend/linkpulse/tests/test_auth.py b/backend/linkpulse/tests/test_auth.py index fba3b15..9ca0ac8 100644 --- a/backend/linkpulse/tests/test_auth.py +++ b/backend/linkpulse/tests/test_auth.py @@ -25,6 +25,7 @@ def test_auth_login(user): response = client.post("/api/login", json=args) assert response.status_code == status.HTTP_200_OK test_expiry(response, 0.5) + assert client.cookies.get("session") is not None # Remember Me, True response = client.post("/api/login", json={**args, "remember_me": True}) From 6ffc84fce3decca11bfeb1ff7ebc1d9a2694e744 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 12:48:04 -0600 Subject: [PATCH 069/100] Logout route, SessionDependency DI model --- backend/linkpulse/dependencies.py | 36 +++++++++++++++++++++++++++++ backend/linkpulse/routers/auth.py | 38 ++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/backend/linkpulse/dependencies.py b/backend/linkpulse/dependencies.py index db24be2..969a3e9 100644 --- a/backend/linkpulse/dependencies.py +++ b/backend/linkpulse/dependencies.py @@ -4,6 +4,8 @@ from fastapi import HTTPException, Request, Response, status from limits.aio.strategies import MovingWindowRateLimiter from limits.aio.storage import MemoryStorage from limits import parse +from linkpulse.models import Session +from dataclasses import dataclass storage = MemoryStorage() strategy = MovingWindowRateLimiter(storage) @@ -39,3 +41,37 @@ class RateLimiter: headers={"Retry-After": self.retry_after}, ) return True + + +@dataclass +class SessionModel: + user_id: str + session_id: str + expires_at: int + + +class SessionDependency: + def __init__(self, required: bool = False): + self.required = required + + async def __call__(self, request: Request, response: Response): + session_token = request.cookies.get("session") + + # If not present, raise 401 if required + if session_token is None: + if self.required: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized") + return None + + # Get session from database + session = Session.get_or_none(Session.token == session_token) + + # This doesn't differentiate between expired or completely invalid sessions + if session is None or session.is_expired(revoke=True): + if self.required: + logger.debug("Session Cookie Revoked", token=session_token) + response.set_cookie("session", "", max_age=0) + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized") + return None + + return session diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index 8ab4e61..ccb247d 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -1,11 +1,9 @@ from datetime import datetime, timedelta -from math import e -from typing import Optional, Tuple +from typing import Annotated, Optional, Tuple import structlog from fastapi import APIRouter, Depends, Response, status -from fastapi.responses import JSONResponse -from linkpulse.dependencies import RateLimiter +from linkpulse.dependencies import SessionDependency, RateLimiter, SessionModel from linkpulse.models import Session, User from linkpulse.utilities import utc_now from pwdlib import PasswordHash @@ -114,13 +112,20 @@ async def login(body: LoginBody, response: Response): return {"email": user.email, "expiry": session.expiry} -@router.post("/api/logout") -async def logout(): - # TODO: Force logout parameter, logout ALL sessions for User - # Get session token from Cookie - # Delete session - # Return 200 - pass +@router.post("/api/logout", status_code=status.HTTP_200_OK) +async def logout( + response: Response, + session: Annotated[Session, Depends(SessionDependency(required=True))], + all: bool = False, +): + # We can assume the session is valid via the dependency + if not all: + session.delete_instance() + else: + count = Session.delete().where(Session.user == session.user).execute() + logger.debug("All sessions deleted", user=session.user.email, count=count) + + response.set_cookie("session", "", max_age=0) @router.post("/api/register") @@ -134,9 +139,16 @@ async def register(): pass +@router.get("/api/session") +async def session(session: Annotated[SessionModel, Depends(SessionDependency(required=True))]): + # Returns the session information for the current session + return {} + + @router.get("/api/sessions") -async def sessions(): - pass +async def sessions(session: Annotated[SessionModel, Depends(SessionDependency(required=True))]): + # Returns a list of all active sessions for this user + return {} # GET /api/user/{id}/sessions From 20a243ddfce30deec89355c2dd22f6eece222783 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 12:48:11 -0600 Subject: [PATCH 070/100] logout route test --- backend/linkpulse/tests/test_auth.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/backend/linkpulse/tests/test_auth.py b/backend/linkpulse/tests/test_auth.py index 9ca0ac8..928ae9c 100644 --- a/backend/linkpulse/tests/test_auth.py +++ b/backend/linkpulse/tests/test_auth.py @@ -4,6 +4,7 @@ from fastapi import status from fastapi.testclient import TestClient from linkpulse.app import app from linkpulse.tests.test_user import user +from linkpulse.tests.test_session import session, expired_session from linkpulse.utilities import utc_now import pytest @@ -43,3 +44,26 @@ def test_auth_login(user): # Wrong Password response = client.post("/api/login", json={**args, "password": "bad_password"}) assert response.status_code == status.HTTP_401_UNAUTHORIZED + + +def test_auth_login_logout(user): + """Test full login & logout cycle""" + args = {"email": user.email, "password": "password"} + + with TestClient(app) as client: + response = client.post("/api/logout") + assert response.status_code == status.HTTP_401_UNAUTHORIZED + + response = client.post("/api/login", json=args) + assert response.status_code == status.HTTP_200_OK + assert client.cookies.get("session") is not None + + response = client.post("/api/logout") + assert response.status_code == status.HTTP_200_OK + assert client.cookies.get("session") is None + + +def test_auth_logout_expired(expired_session): + with TestClient(app) as client: + response = client.post("/api/logout") + assert response.status_code == status.HTTP_401_UNAUTHORIZED From d8659c903fdba5aede578c7d768e588b7fd6c110 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 12:55:53 -0600 Subject: [PATCH 071/100] Fix test_session not using proper generate_token(), fix session_constraint_expiry test_session_constraint_expiry was violating multiple constraints actually, it wasn't properly testing the expiry detail --- backend/linkpulse/tests/test_session.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/backend/linkpulse/tests/test_session.py b/backend/linkpulse/tests/test_session.py index 7a246cf..580109a 100644 --- a/backend/linkpulse/tests/test_session.py +++ b/backend/linkpulse/tests/test_session.py @@ -20,9 +20,7 @@ def db(): @pytest.fixture def session(user): - return Session.create( - user=user, token=random_string(32), expiry=utc_now() + timedelta(hours=1) - ) + return Session.create(user=user, token=Session.generate_token(), expiry=utc_now() + timedelta(hours=1)) @pytest.fixture @@ -55,21 +53,17 @@ def test_expiry_invalid(expired_session): def test_session_constraint_token_length(user): + Session.create(user=user, token=Session.generate_token(), expiry=utc_now() + timedelta(hours=1)) + with pytest.raises(IntegrityError): - Session.create( - user=user, token=random_string(31), expiry=utc_now() + timedelta(hours=1) - ) - Session.create( - user=user, token=random_string(32), expiry=utc_now() + timedelta(hours=1) - ) + Session.create(user=user, token=Session.generate_token()[:-1], expiry=utc_now() + timedelta(hours=1)) def test_session_constraint_expiry(user): + Session.create(user=user, token=Session.generate_token(), expiry=utc_now() + timedelta(minutes=1)) + with pytest.raises(IntegrityError): - Session.create(user=user, token=random_string(31), expiry=utc_now()) - Session.create( - user=user, token=random_string(32), expiry=utc_now() + timedelta(minutes=1) - ) + Session.create(user=user, token=Session.generate_token(), expiry=utc_now()) def test_validate_session(db, session): From 4d6c46a309e483efb3385727c4e236db977fee4c Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 13:00:37 -0600 Subject: [PATCH 072/100] Fix broken expired_session fixture, add test for future --- backend/linkpulse/tests/test_session.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/linkpulse/tests/test_session.py b/backend/linkpulse/tests/test_session.py index 580109a..fdcc9e1 100644 --- a/backend/linkpulse/tests/test_session.py +++ b/backend/linkpulse/tests/test_session.py @@ -25,10 +25,16 @@ def session(user): @pytest.fixture def expired_session(session): + session.created_at = utc_now() - timedelta(hours=2) # Required to bypass the constraint session.expiry = utc_now() - timedelta(hours=1) + session.save() return session +def test_expired_session_fixture(expired_session): + assert expired_session.is_expired() is True + + def test_session_create(session): assert Session.get_or_none(Session.token == session.token) is not None From 1ea3bc20db9a5670b5b8082600fe71a1d7f08a43 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 13:02:02 -0600 Subject: [PATCH 073/100] Add additional logs, finish test_auth_logout_expired --- backend/linkpulse/dependencies.py | 1 + backend/linkpulse/models.py | 1 + backend/linkpulse/routers/auth.py | 3 ++- backend/linkpulse/tests/test_auth.py | 12 ++++++++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/backend/linkpulse/dependencies.py b/backend/linkpulse/dependencies.py index 969a3e9..41f721e 100644 --- a/backend/linkpulse/dependencies.py +++ b/backend/linkpulse/dependencies.py @@ -59,6 +59,7 @@ class SessionDependency: # If not present, raise 401 if required if session_token is None: + logger.debug("No session cookie found", required=self.required) if self.required: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized") return None diff --git a/backend/linkpulse/models.py b/backend/linkpulse/models.py index 57b2725..9782eb2 100644 --- a/backend/linkpulse/models.py +++ b/backend/linkpulse/models.py @@ -91,6 +91,7 @@ class Session(BaseModel): now = utc_now() if self.expiry_utc < now: + logger.debug("Session expired", token=self.token, user=self.user.email, revoke=revoke) if revoke: self.delete_instance() return True diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index ccb247d..275b878 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -121,9 +121,10 @@ async def logout( # We can assume the session is valid via the dependency if not all: session.delete_instance() + logger.debug("Session deleted", user=session.user.email, token=session.token) else: count = Session.delete().where(Session.user == session.user).execute() - logger.debug("All sessions deleted", user=session.user.email, count=count) + logger.debug("All sessions deleted", user=session.user.email, count=count, source_token=session.token) response.set_cookie("session", "", max_age=0) diff --git a/backend/linkpulse/tests/test_auth.py b/backend/linkpulse/tests/test_auth.py index 928ae9c..f65dbb1 100644 --- a/backend/linkpulse/tests/test_auth.py +++ b/backend/linkpulse/tests/test_auth.py @@ -64,6 +64,18 @@ def test_auth_login_logout(user): def test_auth_logout_expired(expired_session): + # Test that an expired session cannot be used to logout, but still removes the cookie with TestClient(app) as client: response = client.post("/api/logout") assert response.status_code == status.HTTP_401_UNAUTHORIZED + + # Add expired session cookie + client.cookies.set("session", expired_session.token) + assert client.cookies.get("session") is not None + + # Attempt to logout + response = client.post("/api/logout") + assert response.status_code == status.HTTP_401_UNAUTHORIZED + assert client.cookies.get("session") is None + + # TODO: Ensure ?all=True doesn't do anything either From 16b70bb0deedcd6aef6b572cbe1c95025afa7e5e Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 13:08:28 -0600 Subject: [PATCH 074/100] Update CHANGELOG.md --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8185777..0a50523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,13 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A release checklist to the `CHANGELOG.md` file, as a reminder for procedure. - An action workflow for invoking `pytest`, with coverage report generation in CI/CD -- backend: Rate Limiting via `limits`, custom `RateLimiter` dependency -- backend: `Session` model constraints for `token` length, `expiry` & `last_used` timestamps -- backend: `pwdlib[argon2]` for password hashing -- backend: provided `LOG_JSON_FORMAT` and `LOG_LEVEL` environment variable defaults in `run.sh` development script +- backend: Rate Limiting via custom `RateLimiter` dependency - backend: `User` model, `Session` model with migration script +- backend: `Session` model constraints for `token` length, `expiry` & `last_used` timestamps +- backend: `SessionDependency` for easy session validation, enforcement & handling per route +- backend: provided `LOG_JSON_FORMAT` and `LOG_LEVEL` environment variable defaults in `run.sh` development script +- backend: Simple `/health` & `/api/migrations` endpoint tests - backend: `utc_now` helper function -- backend: `pytest`, simple `/health` & `/api/migrations` endpoint tests +- backend: `pwdlib[argon2]`, `pytest` (`pytest-cov`, `pytest-xdist`), `limits`, `httpx`, `email-validator` pacakges - frontend: Added `clsx` package ## Changed From 066b3e82ff044cbd84ef6cd2ca60c8d1a8534356 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 13:15:28 -0600 Subject: [PATCH 075/100] reformat, organize imports --- backend/linkpulse/__main__.py | 2 +- backend/linkpulse/app.py | 4 +--- backend/linkpulse/dependencies.py | 7 ++++--- backend/linkpulse/logging.py | 8 ++------ backend/linkpulse/routers/auth.py | 2 +- backend/linkpulse/routers/misc.py | 4 +--- backend/linkpulse/tests/test_auth.py | 6 +++--- backend/linkpulse/tests/test_session.py | 3 +-- 8 files changed, 14 insertions(+), 22 deletions(-) diff --git a/backend/linkpulse/__main__.py b/backend/linkpulse/__main__.py index 45a6b38..5e9ad0a 100644 --- a/backend/linkpulse/__main__.py +++ b/backend/linkpulse/__main__.py @@ -59,7 +59,7 @@ def main(*args: str) -> None: # import most useful objects, models, and functions lp = linkpulse # alias from linkpulse.app import app - from linkpulse.models import BaseModel, User, Session + from linkpulse.models import BaseModel, Session, User from linkpulse.utilities import get_db db = get_db() diff --git a/backend/linkpulse/app.py b/backend/linkpulse/app.py index bde4770..1713d0a 100644 --- a/backend/linkpulse/app.py +++ b/backend/linkpulse/app.py @@ -30,9 +30,7 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]: db.connect() db.create_tables([models.User, models.Session]) - FastAPICache.init( - backend=InMemoryBackend(), prefix="fastapi-cache", cache_status_header="X-Cache" - ) + FastAPICache.init(backend=InMemoryBackend(), prefix="fastapi-cache", cache_status_header="X-Cache") scheduler.start() diff --git a/backend/linkpulse/dependencies.py b/backend/linkpulse/dependencies.py index 41f721e..cedd093 100644 --- a/backend/linkpulse/dependencies.py +++ b/backend/linkpulse/dependencies.py @@ -1,11 +1,12 @@ import os +from dataclasses import dataclass + import structlog from fastapi import HTTPException, Request, Response, status -from limits.aio.strategies import MovingWindowRateLimiter -from limits.aio.storage import MemoryStorage from limits import parse +from limits.aio.storage import MemoryStorage +from limits.aio.strategies import MovingWindowRateLimiter from linkpulse.models import Session -from dataclasses import dataclass storage = MemoryStorage() strategy = MovingWindowRateLimiter(storage) diff --git a/backend/linkpulse/logging.py b/backend/linkpulse/logging.py index b10f7f1..4ee6f68 100644 --- a/backend/linkpulse/logging.py +++ b/backend/linkpulse/logging.py @@ -31,9 +31,7 @@ def drop_color_message_key(_: Any, __: Any, event_dict: EventDict) -> EventDict: return event_dict -def setup_logging( - json_logs: Optional[bool] = None, log_level: Optional[str] = None -) -> None: +def setup_logging(json_logs: Optional[bool] = None, log_level: Optional[str] = None) -> None: # Pull from environment variables, apply defaults if not set json_logs = json_logs or os.getenv("LOG_JSON_FORMAT", "true").lower() == "true" log_level = log_level or os.getenv("LOG_LEVEL", "INFO") @@ -158,8 +156,6 @@ def setup_logging( sys.__excepthook__(exc_type, exc_value, exc_traceback) return - root_logger.error( - "Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback) - ) + root_logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) sys.excepthook = handle_exception diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index 275b878..7039cd8 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -3,7 +3,7 @@ from typing import Annotated, Optional, Tuple import structlog from fastapi import APIRouter, Depends, Response, status -from linkpulse.dependencies import SessionDependency, RateLimiter, SessionModel +from linkpulse.dependencies import RateLimiter, SessionDependency, SessionModel from linkpulse.models import Session, User from linkpulse.utilities import utc_now from pwdlib import PasswordHash diff --git a/backend/linkpulse/routers/misc.py b/backend/linkpulse/routers/misc.py index e91c41e..57b1877 100644 --- a/backend/linkpulse/routers/misc.py +++ b/backend/linkpulse/routers/misc.py @@ -28,8 +28,6 @@ async def get_migration() -> dict[str, Any]: :rtype: dict[str, Any] """ # Kind of insecure, but this is just a demo thing to show that migratehistory is available. - cursor = db.execute_sql( - "SELECT name, migrated_at FROM migratehistory ORDER BY migrated_at DESC LIMIT 1" - ) + cursor = db.execute_sql("SELECT name, migrated_at FROM migratehistory ORDER BY migrated_at DESC LIMIT 1") name, migrated_at = cursor.fetchone() return {"name": name, "migrated_at": migrated_at} diff --git a/backend/linkpulse/tests/test_auth.py b/backend/linkpulse/tests/test_auth.py index f65dbb1..cede613 100644 --- a/backend/linkpulse/tests/test_auth.py +++ b/backend/linkpulse/tests/test_auth.py @@ -1,14 +1,14 @@ from datetime import datetime, timedelta + +import pytest import structlog from fastapi import status from fastapi.testclient import TestClient from linkpulse.app import app +from linkpulse.tests.test_session import expired_session, session from linkpulse.tests.test_user import user -from linkpulse.tests.test_session import session, expired_session from linkpulse.utilities import utc_now -import pytest - logger = structlog.get_logger() diff --git a/backend/linkpulse/tests/test_session.py b/backend/linkpulse/tests/test_session.py index fdcc9e1..ad38ec9 100644 --- a/backend/linkpulse/tests/test_session.py +++ b/backend/linkpulse/tests/test_session.py @@ -3,11 +3,10 @@ from datetime import timedelta import pytest import structlog from linkpulse.models import Session +from linkpulse.routers.auth import validate_session from linkpulse.tests.random import random_string from linkpulse.tests.test_user import user from linkpulse.utilities import utc_now -from linkpulse.routers.auth import validate_session - from peewee import IntegrityError logger = structlog.get_logger() From 1c979ed18a5dcb59f735543b6380d4e746decd97 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 13:17:18 -0600 Subject: [PATCH 076/100] Fix mypy union-attr warning stupid, I checked it twice and there's no possible way for it to be None? --- backend/linkpulse/logging.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/linkpulse/logging.py b/backend/linkpulse/logging.py index 4ee6f68..408d983 100644 --- a/backend/linkpulse/logging.py +++ b/backend/linkpulse/logging.py @@ -33,8 +33,10 @@ def drop_color_message_key(_: Any, __: Any, event_dict: EventDict) -> EventDict: def setup_logging(json_logs: Optional[bool] = None, log_level: Optional[str] = None) -> None: # Pull from environment variables, apply defaults if not set - json_logs = json_logs or os.getenv("LOG_JSON_FORMAT", "true").lower() == "true" - log_level = log_level or os.getenv("LOG_LEVEL", "INFO") + if json_logs is None: + json_logs = os.getenv("LOG_JSON_FORMAT", "true").lower() == "true" + if log_level is None: + log_level = os.getenv("LOG_LEVEL", "INFO") def flatten(n): """ From d6d6e35e535f89d7d4ea2653dfa4ddc74b5455f5 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 13:24:55 -0600 Subject: [PATCH 077/100] Fix Set-Cookie header not propagating into HTTPException, use delete_cookie helper --- backend/linkpulse/dependencies.py | 7 +++++-- backend/linkpulse/routers/auth.py | 2 +- backend/linkpulse/tests/test_auth.py | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/linkpulse/dependencies.py b/backend/linkpulse/dependencies.py index cedd093..8738efc 100644 --- a/backend/linkpulse/dependencies.py +++ b/backend/linkpulse/dependencies.py @@ -72,8 +72,11 @@ class SessionDependency: if session is None or session.is_expired(revoke=True): if self.required: logger.debug("Session Cookie Revoked", token=session_token) - response.set_cookie("session", "", max_age=0) - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized") + response.delete_cookie("session") + headers = {"set-cookie": response.headers["set-cookie"]} + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized", headers=headers + ) return None return session diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index 7039cd8..3f0aca6 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -126,7 +126,7 @@ async def logout( count = Session.delete().where(Session.user == session.user).execute() logger.debug("All sessions deleted", user=session.user.email, count=count, source_token=session.token) - response.set_cookie("session", "", max_age=0) + response.delete_cookie("session", "", max_age=0) @router.post("/api/register") diff --git a/backend/linkpulse/tests/test_auth.py b/backend/linkpulse/tests/test_auth.py index cede613..157b35b 100644 --- a/backend/linkpulse/tests/test_auth.py +++ b/backend/linkpulse/tests/test_auth.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from wsgiref import headers import pytest import structlog @@ -76,6 +77,6 @@ def test_auth_logout_expired(expired_session): # Attempt to logout response = client.post("/api/logout") assert response.status_code == status.HTTP_401_UNAUTHORIZED - assert client.cookies.get("session") is None + assert response.headers.get("set-cookie") is not None # TODO: Ensure ?all=True doesn't do anything either From 30bca75a91d7378f6ee397f97be34622cbe76daa Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 13:49:46 -0600 Subject: [PATCH 078/100] nit, format, varname nit --- backend/linkpulse/migrate.py | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/backend/linkpulse/migrate.py b/backend/linkpulse/migrate.py index e104eda..0ef6df9 100644 --- a/backend/linkpulse/migrate.py +++ b/backend/linkpulse/migrate.py @@ -36,11 +36,7 @@ class ExtendedRouter(Router): try: modules = models if isinstance(module, bool): - modules = [ - m - for _, m, ispkg in pkgutil.iter_modules([f"{router.CURDIR}"]) - if ispkg - ] + modules = [m for _, m, ispkg in pkgutil.iter_modules([f"{router.CURDIR}"]) if ispkg] models = [m for module in modules for m in router.load_models(module)] except ImportError: @@ -48,7 +44,7 @@ class ExtendedRouter(Router): return None if self.ignore: - models = [m for m in models if m._meta.name not in self.ignore] # type: ignore[] + models = [m for m in models if m._meta.name not in self.ignore] # type: ignore for migration in self.diff: self.run_one(migration, self.migrator, fake=True) @@ -91,9 +87,7 @@ def main(*args: str) -> None: diff = router.diff if len(diff) == 0: - logger.info( - "No migrations found, no pending migrations to apply. Creating initial migration." - ) + logger.info("No migrations found, no pending migrations to apply. Creating initial migration.") migration = router.create("initial", auto=target_models) if not migration: @@ -107,13 +101,9 @@ def main(*args: str) -> None: logger.info( "Note: Selecting a migration will apply all migrations up to and including the selected migration." ) - logger.info( - "e.g. Applying 004 while only 001 is applied would apply 002, 003, and 004." - ) + logger.info("e.g. Applying 004 while only 001 is applied would apply 002, 003, and 004.") - choice = questionary.select( - "Select highest migration to apply:", choices=diff - ).ask() + choice = questionary.select("Select highest migration to apply:", choices=diff).ask() if choice is None: logger.warning( "For safety reasons, you won't be able to create migrations without applying the pending ones." @@ -163,14 +153,10 @@ def main(*args: str) -> None: logger.info(f"Migration created: {migration}") if len(router.diff) == 1: - if questionary.confirm( - "Do you want to apply this migration immediately?" - ).ask(): + if questionary.confirm("Do you want to apply this migration immediately?").ask(): router.run(migration) logger.info("Done.") - logger.warning( - "!!! Commit and push this migration file immediately!" - ) + logger.warning("!!! Commit and push this migration file immediately!") else: raise RuntimeError( "Changes anticipated with show() but no migration created with create(), model definition may have reverted." @@ -178,10 +164,10 @@ def main(*args: str) -> None: else: logger.info("No database changes detected.") - migration_limit: int = 15 - if len(current) > migration_limit: + migration_squash_threshold: int = 15 + if len(current) > migration_squash_threshold: if questionary.confirm( - f"There are more than {migration_limit} migrations applied. Do you want to merge them?", + f"There are more than {migration_squash_threshold} migrations applied. Do you want to merge them?", default=False, ).ask(): logger.info("Merging migrations...") From 01081ee83451f76d8fed20250b97107419b627cf Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 13:50:07 -0600 Subject: [PATCH 079/100] begin adding shadcn components for frontend --- CHANGELOG.md | 1 + frontend/components.json | 21 +++++++ frontend/package.json | 7 ++- frontend/pnpm-lock.yaml | 82 +++++++++++++++++++++++++++ frontend/src/App.css | 6 ++ frontend/src/components/ui/button.tsx | 57 +++++++++++++++++++ frontend/tailwind.config.cjs | 19 +++++-- frontend/tsconfig.json | 6 +- frontend/vite.config.ts | 6 ++ 9 files changed, 197 insertions(+), 8 deletions(-) create mode 100644 frontend/components.json create mode 100644 frontend/src/components/ui/button.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a50523..f9a878c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A release checklist to the `CHANGELOG.md` file, as a reminder for procedure. - An action workflow for invoking `pytest`, with coverage report generation in CI/CD +- backend: Login & Logout routes - backend: Rate Limiting via custom `RateLimiter` dependency - backend: `User` model, `Session` model with migration script - backend: `Session` model constraints for `token` length, `expiry` & `last_used` timestamps diff --git a/frontend/components.json b/frontend/components.json new file mode 100644 index 0000000..2d871b9 --- /dev/null +++ b/frontend/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.cjs", + "css": "src/App.css", + "baseColor": "zinc", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 255c944..e54e173 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,9 +8,14 @@ "pnpm": ">=9.0.0" }, "dependencies": { + "@radix-ui/react-slot": "^1.1.0", + "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "lucide-react": "^0.456.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "tailwind-merge": "^2.5.4", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@ianvs/prettier-plugin-sort-imports": "^4.2.1", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 31dc745..97b339e 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,15 +8,30 @@ importers: .: dependencies: + '@radix-ui/react-slot': + specifier: ^1.1.0 + version: 1.1.0(@types/react@18.3.11)(react@18.3.1) + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 clsx: specifier: ^2.1.1 version: 2.1.1 + lucide-react: + specifier: ^0.456.0 + version: 0.456.0(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + tailwind-merge: + specifier: ^2.5.4 + version: 2.5.4 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3))) devDependencies: '@ianvs/prettier-plugin-sort-imports': specifier: ^4.2.1 @@ -417,6 +432,24 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@radix-ui/react-compose-refs@1.1.0': + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.1.0': + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@rollup/rollup-android-arm-eabi@4.24.0': resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} cpu: [arm] @@ -766,6 +799,13 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + class-variance-authority@0.7.0: + resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + + clsx@2.0.0: + resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} + engines: {node: '>=6'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1141,6 +1181,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.456.0: + resolution: {integrity: sha512-DIIGJqTT5X05sbAsQ+OhA8OtJYyD4NsEMCA/HQW/Y6ToPQ7gwbtujIoeAaup4HpHzV35SQOarKAWH8LYglB6eA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + magic-string@0.30.12: resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} @@ -1548,6 +1593,14 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tailwind-merge@2.5.4: + resolution: {integrity: sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + tailwindcss@3.4.14: resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==} engines: {node: '>=14.0.0'} @@ -2056,6 +2109,19 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.11)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.11 + + '@radix-ui/react-slot@1.1.0(@types/react@18.3.11)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.11 + '@rollup/rollup-android-arm-eabi@4.24.0': optional: true @@ -2375,6 +2441,12 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + class-variance-authority@0.7.0: + dependencies: + clsx: 2.0.0 + + clsx@2.0.0: {} + clsx@2.1.1: {} color-convert@1.9.3: @@ -2738,6 +2810,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@0.456.0(react@18.3.1): + dependencies: + react: 18.3.1 + magic-string@0.30.12: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -3071,6 +3147,12 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + tailwind-merge@2.5.4: {} + + tailwindcss-animate@1.0.7(tailwindcss@3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3))): + dependencies: + tailwindcss: 3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3)) + tailwindcss@3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3)): dependencies: '@alloc/quick-lru': 5.2.0 diff --git a/frontend/src/App.css b/frontend/src/App.css index dacd573..df9d556 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -18,3 +18,9 @@ body { background-color: var(--background-color); color: var(--text-color); } + +@layer base { + :root { + --radius: 0.5rem; + } +} diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx new file mode 100644 index 0000000..4f52e23 --- /dev/null +++ b/frontend/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-zinc-950 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 dark:focus-visible:ring-zinc-300", + { + variants: { + variant: { + default: + "bg-zinc-900 text-zinc-50 shadow hover:bg-zinc-900/90 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/90", + destructive: + "bg-red-500 text-zinc-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/90", + outline: + "border border-zinc-200 bg-white shadow-sm hover:bg-zinc-100 hover:text-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-800 dark:hover:text-zinc-50", + secondary: + "bg-zinc-100 text-zinc-900 shadow-sm hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80", + ghost: "hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800 dark:hover:text-zinc-50", + link: "text-zinc-900 underline-offset-4 hover:underline dark:text-zinc-50", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs index a5782a2..612174a 100644 --- a/frontend/tailwind.config.cjs +++ b/frontend/tailwind.config.cjs @@ -1,13 +1,20 @@ /** @type {import('tailwindcss').Config} */ module.exports = { content: ['./src/**/*.{js,jsx,ts,tsx}', './*.html'], - darkMode: 'media', + darkMode: ['media', 'class'], mode: 'jit', theme: { - extend: { - fontFamily: { - inter: ['Inter', 'sans-serif'], - }, - }, + extend: { + fontFamily: { + inter: ['Inter', 'sans-serif'] + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + colors: {} + } }, + plugins: [require("tailwindcss-animate")] }; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 6b6b7d7..d50276b 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -17,7 +17,11 @@ "skipLibCheck": true, "strict": true, "target": "es2017", - "types": ["vite/client"] + "types": ["vite/client"], + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } }, "exclude": ["node_modules"], "include": ["**/*.ts", "**/*.tsx"], diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index fabde1a..78caf99 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,6 +1,12 @@ +import path from 'path'; import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, }); From 69c83edd3a870648664405e889bbc5374341d6a6 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 14:02:47 -0600 Subject: [PATCH 080/100] completely re-initialize Vite/React configuration to dummy --- frontend/.gitignore | 30 +- frontend/components.json | 6 +- frontend/eslint.config.js | 28 + frontend/index.html | 17 +- frontend/package.json | 71 +- frontend/pnpm-lock.yaml | 1670 ++++++----------- .../{postcss.config.cjs => postcss.config.js} | 6 +- frontend/src/App.css | 54 +- frontend/src/App.tsx | 112 +- frontend/src/index.css | 137 ++ frontend/src/index.tsx | 10 - frontend/src/main.tsx | 10 + frontend/src/vite-env.d.ts | 1 + frontend/tailwind.config.cjs | 20 - frontend/tailwind.config.js | 57 + frontend/tsconfig.app.json | 30 + frontend/tsconfig.json | 34 +- frontend/tsconfig.node.json | 28 + frontend/vite.config.ts | 11 +- 19 files changed, 961 insertions(+), 1371 deletions(-) create mode 100644 frontend/eslint.config.js rename frontend/{postcss.config.cjs => postcss.config.js} (73%) create mode 100644 frontend/src/index.css delete mode 100644 frontend/src/index.tsx create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/vite-env.d.ts delete mode 100644 frontend/tailwind.config.cjs create mode 100644 frontend/tailwind.config.js create mode 100644 frontend/tsconfig.app.json create mode 100644 frontend/tsconfig.node.json diff --git a/frontend/.gitignore b/frontend/.gitignore index ffde942..a547bf3 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,6 +1,24 @@ -.eslintcache -.pnpm-debug.log -node_modules/ -coverage/ -dist/ -tsconfig.tsbuildinfo +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/components.json b/frontend/components.json index 2d871b9..51d59d2 100644 --- a/frontend/components.json +++ b/frontend/components.json @@ -4,10 +4,10 @@ "rsc": false, "tsx": true, "tailwind": { - "config": "tailwind.config.cjs", - "css": "src/App.css", + "config": "tailwind.config.js", + "css": "src/index.css", "baseColor": "zinc", - "cssVariables": false, + "cssVariables": true, "prefix": "" }, "aliases": { diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/frontend/index.html b/frontend/index.html index 55583a3..e4b78ea 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,16 +1,13 @@ - - - - App + + + + Vite + React + TS -
- +
+ - \ No newline at end of file + diff --git a/frontend/package.json b/frontend/package.json index e54e173..8d099e4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,14 +1,20 @@ { "name": "linkpulse", + "private": true, "version": "0.3.0", - "author": "Xevion ", + "author": { + "name": "Xevion", + "url": "https://xevion.dev", + "email": "xevion@xevion.dev" + }, "type": "module", - "engines": { - "node": ">=22.0.0", - "pnpm": ">=9.0.0" + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" }, "dependencies": { - "@radix-ui/react-slot": "^1.1.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-react": "^0.456.0", @@ -18,46 +24,21 @@ "tailwindcss-animate": "^1.0.7" }, "devDependencies": { - "@ianvs/prettier-plugin-sort-imports": "^4.2.1", - "@swc/core": "^1.6.5", + "@eslint/js": "^9.13.0", "@types/node": "^22.9.0", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "autoprefixer": "^10.4.19", - "eslint": "^9", - "npm-run-all2": "^6.2.0", - "postcss": "^8.4.38", - "prettier": "^3.3.2", - "prettier-plugin-tailwindcss": "^0.6.5", - "tailwindcss": "^3.4.4", - "ts-node": "^10.9.2", - "typescript": "^5.5.2", - "vite": "^5.3.1", - "vitest": "^1.6.0" - }, - "scripts": { - "preinstall": "command -v git >/dev/null 2>&1 && git config core.hooksPath git-hooks || true", - "build": "vite build", - "dev:update-deps": "rm -rf pnpm-lock.yaml node_modules/ **/node_modules && pnpm install", - "dev": "vite dev", - "format": "prettier --write .", - "lint:format": "prettier --cache --check .", - "lint": "eslint --cache .", - "test": "npm-run-all --parallel tsc:check vitest:run lint lint:format", - "tsc:check": "tsc", - "vitest:run": "vitest run" - }, - "browserslist": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "pnpm": { - "updateConfig": { - "ignoreDependencies": [ - "eslint" - ] - } + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react-swc": "^3.5.0", + "autoprefixer": "^10.4.20", + "eslint": "^9.13.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.11.0", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14", + "typescript": "~5.6.2", + "typescript-eslint": "^8.11.0", + "vite": "^5.4.10", + "vitest": "^2.1.4" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 97b339e..8aac54a 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - '@radix-ui/react-slot': - specifier: ^1.1.0 - version: 1.1.0(@types/react@18.3.11)(react@18.3.1) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -31,59 +28,56 @@ importers: version: 2.5.4 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3))) + version: 1.0.7(tailwindcss@3.4.14) devDependencies: - '@ianvs/prettier-plugin-sort-imports': - specifier: ^4.2.1 - version: 4.3.1(prettier@3.3.3) - '@swc/core': - specifier: ^1.6.5 - version: 1.7.36 + '@eslint/js': + specifier: ^9.13.0 + version: 9.14.0 '@types/node': specifier: ^22.9.0 version: 22.9.0 '@types/react': - specifier: ^18.3.3 - version: 18.3.11 + specifier: ^18.3.12 + version: 18.3.12 '@types/react-dom': - specifier: ^18.3.0 + specifier: ^18.3.1 version: 18.3.1 - '@vitejs/plugin-react': - specifier: ^4.3.1 - version: 4.3.2(vite@5.4.9(@types/node@22.9.0)) + '@vitejs/plugin-react-swc': + specifier: ^3.5.0 + version: 3.7.1(vite@5.4.10(@types/node@22.9.0)) autoprefixer: - specifier: ^10.4.19 + specifier: ^10.4.20 version: 10.4.20(postcss@8.4.47) eslint: - specifier: ^9 + specifier: ^9.13.0 version: 9.14.0(jiti@1.21.6) - npm-run-all2: - specifier: ^6.2.0 - version: 6.2.3 + eslint-plugin-react-hooks: + specifier: ^5.0.0 + version: 5.0.0(eslint@9.14.0(jiti@1.21.6)) + eslint-plugin-react-refresh: + specifier: ^0.4.14 + version: 0.4.14(eslint@9.14.0(jiti@1.21.6)) + globals: + specifier: ^15.11.0 + version: 15.12.0 postcss: - specifier: ^8.4.38 + specifier: ^8.4.47 version: 8.4.47 - prettier: - specifier: ^3.3.2 - version: 3.3.3 - prettier-plugin-tailwindcss: - specifier: ^0.6.5 - version: 0.6.8(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3))(prettier@3.3.3) tailwindcss: - specifier: ^3.4.4 - version: 3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3)) - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3) + specifier: ^3.4.14 + version: 3.4.14 typescript: - specifier: ^5.5.2 + specifier: ~5.6.2 version: 5.6.3 + typescript-eslint: + specifier: ^8.11.0 + version: 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) vite: - specifier: ^5.3.1 - version: 5.4.9(@types/node@22.9.0) + specifier: ^5.4.10 + version: 5.4.10(@types/node@22.9.0) vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@22.9.0) + specifier: ^2.1.4 + version: 2.1.4(@types/node@22.9.0) packages: @@ -91,101 +85,6 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@babel/code-frame@7.25.7': - resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.25.8': - resolution: {integrity: sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.25.8': - resolution: {integrity: sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.25.7': - resolution: {integrity: sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.25.7': - resolution: {integrity: sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.25.7': - resolution: {integrity: sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.25.7': - resolution: {integrity: sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.25.7': - resolution: {integrity: sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-simple-access@7.25.7': - resolution: {integrity: sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.25.7': - resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.25.7': - resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.25.7': - resolution: {integrity: sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.25.7': - resolution: {integrity: sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.25.7': - resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.25.8': - resolution: {integrity: sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-transform-react-jsx-self@7.25.7': - resolution: {integrity: sha512-JD9MUnLbPL0WdVK8AWC7F7tTG2OS6u/AKKnsK+NdRhUiVdnzyR1S3kKQCaRLOiaULvUiqK6Z4JQE635VgtCFeg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.25.7': - resolution: {integrity: sha512-S/JXG/KrbIY06iyJPKfxr0qRxnhNOdkNXYBl/rmwgDd72cQLH9tEGkDm/yJPGvcSIUoikzfjMios9i+xT/uv9w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/template@7.25.7': - resolution: {integrity: sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.25.7': - resolution: {integrity: sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.25.8': - resolution: {integrity: sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==} - engines: {node: '>=6.9.0'} - - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -324,8 +223,8 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.4.0': - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -378,23 +277,10 @@ packages: resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} engines: {node: '>=18.18'} - '@ianvs/prettier-plugin-sort-imports@4.3.1': - resolution: {integrity: sha512-ZHwbyjkANZOjaBm3ZosADD2OUYGFzQGxfy67HmGZU94mHqe7g1LCMA7YYKB1Cq+UTPCBqlAYapY0KXAjKEw8Sg==} - peerDependencies: - '@vue/compiler-sfc': 2.7.x || 3.x - prettier: 2 || 3 - peerDependenciesMeta: - '@vue/compiler-sfc': - optional: true - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -413,9 +299,6 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -432,169 +315,158 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@radix-ui/react-compose-refs@1.1.0': - resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-slot@1.1.0': - resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@rollup/rollup-android-arm-eabi@4.24.0': - resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} + '@rollup/rollup-android-arm-eabi@4.25.0': + resolution: {integrity: sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.24.0': - resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} + '@rollup/rollup-android-arm64@4.25.0': + resolution: {integrity: sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.24.0': - resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} + '@rollup/rollup-darwin-arm64@4.25.0': + resolution: {integrity: sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.24.0': - resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} + '@rollup/rollup-darwin-x64@4.25.0': + resolution: {integrity: sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': - resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} + '@rollup/rollup-freebsd-arm64@4.25.0': + resolution: {integrity: sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.25.0': + resolution: {integrity: sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.25.0': + resolution: {integrity: sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.24.0': - resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} + '@rollup/rollup-linux-arm-musleabihf@4.25.0': + resolution: {integrity: sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.24.0': - resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} + '@rollup/rollup-linux-arm64-gnu@4.25.0': + resolution: {integrity: sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.24.0': - resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} + '@rollup/rollup-linux-arm64-musl@4.25.0': + resolution: {integrity: sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': - resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.25.0': + resolution: {integrity: sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.24.0': - resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} + '@rollup/rollup-linux-riscv64-gnu@4.25.0': + resolution: {integrity: sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.24.0': - resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} + '@rollup/rollup-linux-s390x-gnu@4.25.0': + resolution: {integrity: sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.24.0': - resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} + '@rollup/rollup-linux-x64-gnu@4.25.0': + resolution: {integrity: sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.24.0': - resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} + '@rollup/rollup-linux-x64-musl@4.25.0': + resolution: {integrity: sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.24.0': - resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} + '@rollup/rollup-win32-arm64-msvc@4.25.0': + resolution: {integrity: sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.24.0': - resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} + '@rollup/rollup-win32-ia32-msvc@4.25.0': + resolution: {integrity: sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.24.0': - resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} + '@rollup/rollup-win32-x64-msvc@4.25.0': + resolution: {integrity: sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==} cpu: [x64] os: [win32] - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - - '@swc/core-darwin-arm64@1.7.36': - resolution: {integrity: sha512-8vDczXzCgv3ceTPhEivlpGprN44YlrCK1nbfU9g2TrhV/Aiqi09W/eM5zLesdoM1Z3mJl492gc/8nlTkpDdusw==} + '@swc/core-darwin-arm64@1.9.1': + resolution: {integrity: sha512-2/ncHSCdAh5OHem1fMITrWEzzl97OdMK1PHc9CkxSJnphLjRubfxB5sbc5tDhcO68a5tVy+DxwaBgDec3PXnOg==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.7.36': - resolution: {integrity: sha512-Pa2Gao7+Wf5m3SsK4abKRtd48AtoUnJInvaC3d077swBfgZjbjUbQvcpdc2dOeQtWwo49rFqUZJonMsL0jnPgQ==} + '@swc/core-darwin-x64@1.9.1': + resolution: {integrity: sha512-4MDOFC5zmNqRJ9RGFOH95oYf27J9HniLVpB1pYm2gGeNHdl2QvDMtx2QTuMHQ6+OTn/3y1BHYuhBGp7d405oLA==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.7.36': - resolution: {integrity: sha512-3YsMWd7V+WZEjbfBnLkkz/olcRBa8nyoK0iIOnNARJBMcYaJxjkJSMZpmSojCnIVwvjA1N83CPAbUL+W+fCnHg==} + '@swc/core-linux-arm-gnueabihf@1.9.1': + resolution: {integrity: sha512-eVW/BjRW8/HpLe3+1jRU7w7PdRLBgnEEYTkHJISU8805/EKT03xNZn6CfaBpKfeAloY4043hbGzE/NP9IahdpQ==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.7.36': - resolution: {integrity: sha512-lqM3aBB7kJazJYOwHeA5OGNLqXoQPZ/76b3dV+XcjN1GhD0CcXz6mW5PRYVin6OSN1eKrKBKJjtDA1mqADDEvw==} + '@swc/core-linux-arm64-gnu@1.9.1': + resolution: {integrity: sha512-8m3u1v8R8NgI/9+cHMkzk14w87blSy3OsQPWPfhOL+XPwhyLPvat+ahQJb2nZmltjTgkB4IbzKFSfbuA34LmNA==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.7.36': - resolution: {integrity: sha512-bqei2YDzvUfG0pth5W2xJaj0eG4XWYk0d/NJ75vBX6bkIzK6dC8iuKQ41jOfUWonnrAs7rTDDJW0sTn/evvRdw==} + '@swc/core-linux-arm64-musl@1.9.1': + resolution: {integrity: sha512-hpT0sQAZnW8l02I289yeyFfT9llGO9PzKDxUq8pocKtioEHiElRqR53juCWoSmzuWi+6KX7zUJ0NKCBrc8pmDg==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.7.36': - resolution: {integrity: sha512-03maXTUyaBjeCxlDltmdzHje1ryQt1C4OWmmNgSSRXjLb+GNnAenwOJMSrcvHP/aNClD2pwsFCnYKDGy+sYE6w==} + '@swc/core-linux-x64-gnu@1.9.1': + resolution: {integrity: sha512-sGFdpdAYusk/ropHiwtXom2JrdaKPxl8MqemRv6dvxZq1Gm/GdmOowxdXIPjCgBGMgoXVcgNviH6CgiO5q+UtA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.7.36': - resolution: {integrity: sha512-XXysqLkvjtQnXm1zHqLhy00UYPv/gk5OtwR732X+piNisnEbcJBqI8Qp9O7YvLWllRcoP8IMBGDWLGdGLSpViA==} + '@swc/core-linux-x64-musl@1.9.1': + resolution: {integrity: sha512-YtNLNwIWs0Z2+XgBs6+LrCIGtfCDtNr4S4b6Q5HDOreEIGzSvhkef8eyBI5L+fJ2eGov4b7iEo61C4izDJS5RA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.7.36': - resolution: {integrity: sha512-k7+dmb13a/zPw+E4XYfPmLZFWJgcOcBRKIjYl9nQErtYsgsg3Ji6TBbsvJVETy23lNHyewZ17V5Vq6NzaG0hzg==} + '@swc/core-win32-arm64-msvc@1.9.1': + resolution: {integrity: sha512-qSxD3uZW2vSiHqUt30vUi0PB92zDh9bjqh5YKpfhhVa7h1vt/xXhlid8yMvSNToTfzhRrTEffOAPUr7WVoyQUA==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.7.36': - resolution: {integrity: sha512-ridD3ay6YM2PEYHZXXFN+edYEv0FOynaqOBP+NSnGNHA35azItIjoIe+KNi4WltGtAjpKCHSpjGCNfna12wdYQ==} + '@swc/core-win32-ia32-msvc@1.9.1': + resolution: {integrity: sha512-C3fPEwyX/WRPlX6zIToNykJuz1JkZX0sk8H1QH2vpnKuySUkt/Ur5K2FzLgSWzJdbfxstpgS151/es0VGAD+ZA==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.7.36': - resolution: {integrity: sha512-j1z2Z1Ln9d0E3dHsPkC1K9XDh0ojhRPwV+GfRTu4D61PE+aYhYLvbJC6xPvL4/204QrStRS7eDu3m+BcDp3rgQ==} + '@swc/core-win32-x64-msvc@1.9.1': + resolution: {integrity: sha512-2XZ+U1AyVsOAXeH6WK1syDm7+gwTjA8fShs93WcbxnK7HV+NigDlvr4124CeJLTHyh3fMh1o7+CnQnaBJhlysQ==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.7.36': - resolution: {integrity: sha512-bu7ymMX+LCJOSSrKank25Jaq66ymLVA9fOUuy4ck3/6rbXdLw+pIJPnIDKQ9uNcxww8KDxOuJk9Ui9pqR+aGFw==} + '@swc/core@1.9.1': + resolution: {integrity: sha512-OnPc+Kt5oy3xTvr/KCUOqE9ptJcWbyQgAUr1ydh9EmbBcmJTaO1kfQCxm/axzJi6sKeDTxL9rX5zvLOhoYIaQw==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '*' @@ -605,32 +477,8 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/types@0.1.13': - resolution: {integrity: sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==} - - '@tsconfig/node10@1.0.11': - resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + '@swc/types@0.1.14': + resolution: {integrity: sha512-PbSmTiYCN+GMrvfjrMo9bdY+f2COnwbdnoMw7rqU/PI5jXpKjxOGZ0qqZCImxnT81NkNsKnmEpvu+hRXLBeCJg==} '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -647,44 +495,105 @@ packages: '@types/react-dom@18.3.1': resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} - '@types/react@18.3.11': - resolution: {integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==} + '@types/react@18.3.12': + resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} - '@vitejs/plugin-react@4.3.2': - resolution: {integrity: sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==} - engines: {node: ^14.18.0 || >=16.0.0} + '@typescript-eslint/eslint-plugin@8.13.0': + resolution: {integrity: sha512-nQtBLiZYMUPkclSeC3id+x4uVd1SGtHuElTxL++SfP47jR0zfkZBJHc+gL4qPsgTuypz0k8Y2GheaDYn6Gy3rg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - '@vitest/expect@1.6.0': - resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} + '@typescript-eslint/parser@8.13.0': + resolution: {integrity: sha512-w0xp+xGg8u/nONcGw1UXAr6cjCPU1w0XVyBs6Zqaj5eLmxkKQAByTdV/uGgNN5tVvN/kKpoQlP2cL7R+ajZZIQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - '@vitest/runner@1.6.0': - resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} + '@typescript-eslint/scope-manager@8.13.0': + resolution: {integrity: sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/snapshot@1.6.0': - resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} + '@typescript-eslint/type-utils@8.13.0': + resolution: {integrity: sha512-Rqnn6xXTR316fP4D2pohZenJnp+NwQ1mo7/JM+J1LWZENSLkJI8ID8QNtlvFeb0HnFSK94D6q0cnMX6SbE5/vA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - '@vitest/spy@1.6.0': - resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + '@typescript-eslint/types@8.13.0': + resolution: {integrity: sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/utils@1.6.0': - resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + '@typescript-eslint/typescript-estree@8.13.0': + resolution: {integrity: sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@8.13.0': + resolution: {integrity: sha512-A1EeYOND6Uv250nybnLZapeXpYMl8tkzYUxqmoKAWnI4sei3ihf2XdZVd+vVOmHGcp3t+P7yRrNsyyiXTvShFQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + '@typescript-eslint/visitor-keys@8.13.0': + resolution: {integrity: sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react-swc@3.7.1': + resolution: {integrity: sha512-vgWOY0i1EROUK0Ctg1hwhtC3SdcDjZcdit4Ups4aPkDcB1jYhmo+RMYWY87cmXMhvtD5uf8lV89j2w16vkdSVg==} + peerDependencies: + vite: ^4 || ^5 + + '@vitest/expect@2.1.4': + resolution: {integrity: sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==} + + '@vitest/mocker@2.1.4': + resolution: {integrity: sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.4': + resolution: {integrity: sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==} + + '@vitest/runner@2.1.4': + resolution: {integrity: sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==} + + '@vitest/snapshot@2.1.4': + resolution: {integrity: sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==} + + '@vitest/spy@2.1.4': + resolution: {integrity: sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==} + + '@vitest/utils@2.1.4': + resolution: {integrity: sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.14.0: resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} @@ -701,18 +610,10 @@ packages: resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -724,17 +625,15 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} autoprefixer@10.4.20: resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} @@ -760,8 +659,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.24.0: - resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==} + browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -777,23 +676,20 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001668: - resolution: {integrity: sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==} + caniuse-lite@1.0.30001679: + resolution: {integrity: sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==} - chai@4.5.0: - resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} - engines: {node: '>=4'} - - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} @@ -810,16 +706,10 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -830,17 +720,8 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.5: + resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} engines: {node: '>= 8'} cssesc@3.0.0: @@ -860,8 +741,8 @@ packages: supports-color: optional: true - deep-eql@4.1.4: - resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} deep-is@0.1.4: @@ -870,22 +751,14 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.39: - resolution: {integrity: sha512-4xkpSR6CjuiaNyvwiWDI85N9AxsvbPawB8xc7yzLPonYTuP19BVgYweKyUMFtHEZgIcHWMt1ks5Cqx2m+6/Grg==} + electron-to-chromium@1.5.55: + resolution: {integrity: sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -902,14 +775,21 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-plugin-react-hooks@5.0.0: + resolution: {integrity: sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.14: + resolution: {integrity: sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==} + peerDependencies: + eslint: '>=7' + eslint-scope@8.2.0: resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -955,9 +835,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1009,17 +889,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1032,17 +901,16 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} + globals@15.12.0: + resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==} + engines: {node: '>=18'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -1052,10 +920,6 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1092,10 +956,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1109,36 +969,19 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-tokens@9.0.0: - resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} - js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsesc@3.0.2: - resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} - engines: {node: '>=6'} - hasBin: true - json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-parse-even-better-errors@3.0.2: - resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1157,10 +1000,6 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} - locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1172,15 +1011,12 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@0.456.0: resolution: {integrity: sha512-DIIGJqTT5X05sbAsQ+OhA8OtJYyD4NsEMCA/HQW/Y6ToPQ7gwbtujIoeAaup4HpHzV35SQOarKAWH8LYglB6eA==} peerDependencies: @@ -1189,16 +1025,6 @@ packages: magic-string@0.30.12: resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - - memorystream@0.3.1: - resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} - engines: {node: '>= 0.10.0'} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1207,10 +1033,6 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1222,9 +1044,6 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - mlly@1.7.2: - resolution: {integrity: sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1250,19 +1069,6 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} - npm-normalize-package-bin@3.0.1: - resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-run-all2@6.2.3: - resolution: {integrity: sha512-5RsxC7jEc/RjxOYBVdEfrJf5FsJ0pHA7jr2/OxrThXknajETCTYjigOCG3iaGjdYIKEQlDuCG0ir0T1HTva8pg==} - engines: {node: ^14.18.0 || ^16.13.0 || >=18.0.0, npm: '>= 8'} - hasBin: true - - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1271,10 +1077,6 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1283,10 +1085,6 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -1306,10 +1104,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -1320,21 +1114,17 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} - picocolors@1.1.0: - resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true - pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -1343,9 +1133,6 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - pkg-types@1.2.1: - resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} - postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -1391,70 +1178,6 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier-plugin-tailwindcss@0.6.8: - resolution: {integrity: sha512-dGu3kdm7SXPkiW4nzeWKCl3uoImdd5CTZEJGxyypEPL37Wj0HT2pLqjrvSei1nTeuQfO4PUfjeW5cTUNRLZ4sA==} - engines: {node: '>=14.21.3'} - peerDependencies: - '@ianvs/prettier-plugin-sort-imports': '*' - '@prettier/plugin-pug': '*' - '@shopify/prettier-plugin-liquid': '*' - '@trivago/prettier-plugin-sort-imports': '*' - '@zackad/prettier-plugin-twig-melody': '*' - prettier: ^3.0 - prettier-plugin-astro: '*' - prettier-plugin-css-order: '*' - prettier-plugin-import-sort: '*' - prettier-plugin-jsdoc: '*' - prettier-plugin-marko: '*' - prettier-plugin-multiline-arrays: '*' - prettier-plugin-organize-attributes: '*' - prettier-plugin-organize-imports: '*' - prettier-plugin-sort-imports: '*' - prettier-plugin-style-order: '*' - prettier-plugin-svelte: '*' - peerDependenciesMeta: - '@ianvs/prettier-plugin-sort-imports': - optional: true - '@prettier/plugin-pug': - optional: true - '@shopify/prettier-plugin-liquid': - optional: true - '@trivago/prettier-plugin-sort-imports': - optional: true - '@zackad/prettier-plugin-twig-melody': - optional: true - prettier-plugin-astro: - optional: true - prettier-plugin-css-order: - optional: true - prettier-plugin-import-sort: - optional: true - prettier-plugin-jsdoc: - optional: true - prettier-plugin-marko: - optional: true - prettier-plugin-multiline-arrays: - optional: true - prettier-plugin-organize-attributes: - optional: true - prettier-plugin-organize-imports: - optional: true - prettier-plugin-sort-imports: - optional: true - prettier-plugin-style-order: - optional: true - prettier-plugin-svelte: - optional: true - - prettier@3.3.3: - resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} - engines: {node: '>=14'} - hasBin: true - - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1467,13 +1190,6 @@ packages: peerDependencies: react: ^18.3.1 - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} - engines: {node: '>=0.10.0'} - react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -1481,10 +1197,6 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - read-package-json-fast@3.0.2: - resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -1501,8 +1213,8 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.24.0: - resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} + rollup@4.25.0: + resolution: {integrity: sha512-uVbClXmR6wvx5R1M3Od4utyLUxrmOcEm3pAtMphn73Apq19PDtHpgZoEvqH2YnnaNUuvKmg2DgRd2Sqv+odyqg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1512,10 +1224,6 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} @@ -1529,9 +1237,6 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.1: - resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} - siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1546,8 +1251,8 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -1565,26 +1270,15 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strip-literal@2.1.0: - resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} - sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1619,55 +1313,52 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} + tinyexec@0.3.1: + resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + + tinypool@1.0.1: + resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} - tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + ts-api-utils@1.4.0: + resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-detect@4.1.0: - resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} - engines: {node: '>=4'} + typescript-eslint@8.13.0: + resolution: {integrity: sha512-vIMpDRJrQd70au2G8w34mPps0ezFSPMEX4pXkTzUkrNbRX+36ais2ksGWN0esZL+ZMaFJEneOBHzCgSqle7DHw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true typescript@5.6.3: resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true - ufo@1.5.4: - resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} - undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} @@ -1683,16 +1374,13 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - - vite-node@1.6.0: - resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} + vite-node@2.1.4: + resolution: {integrity: sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.4.9: - resolution: {integrity: sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==} + vite@5.4.10: + resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -1722,15 +1410,15 @@ packages: terser: optional: true - vitest@1.6.0: - resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} + vitest@2.1.4: + resolution: {integrity: sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.0 - '@vitest/ui': 1.6.0 + '@vitest/browser': 2.1.4 + '@vitest/ui': 2.1.4 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -1769,163 +1457,19 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@2.6.0: resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} engines: {node: '>= 14'} hasBin: true - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-queue@1.1.1: - resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} - engines: {node: '>=12.20'} - snapshots: '@alloc/quick-lru@5.2.0': {} - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - - '@babel/code-frame@7.25.7': - dependencies: - '@babel/highlight': 7.25.7 - picocolors: 1.1.0 - - '@babel/compat-data@7.25.8': {} - - '@babel/core@7.25.8': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.25.7 - '@babel/generator': 7.25.7 - '@babel/helper-compilation-targets': 7.25.7 - '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) - '@babel/helpers': 7.25.7 - '@babel/parser': 7.25.8 - '@babel/template': 7.25.7 - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.8 - convert-source-map: 2.0.0 - debug: 4.3.7 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.25.7': - dependencies: - '@babel/types': 7.25.8 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.0.2 - - '@babel/helper-compilation-targets@7.25.7': - dependencies: - '@babel/compat-data': 7.25.8 - '@babel/helper-validator-option': 7.25.7 - browserslist: 4.24.0 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-module-imports@7.25.7': - dependencies: - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.8 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.25.7(@babel/core@7.25.8)': - dependencies: - '@babel/core': 7.25.8 - '@babel/helper-module-imports': 7.25.7 - '@babel/helper-simple-access': 7.25.7 - '@babel/helper-validator-identifier': 7.25.7 - '@babel/traverse': 7.25.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.25.7': {} - - '@babel/helper-simple-access@7.25.7': - dependencies: - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.8 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.25.7': {} - - '@babel/helper-validator-identifier@7.25.7': {} - - '@babel/helper-validator-option@7.25.7': {} - - '@babel/helpers@7.25.7': - dependencies: - '@babel/template': 7.25.7 - '@babel/types': 7.25.8 - - '@babel/highlight@7.25.7': - dependencies: - '@babel/helper-validator-identifier': 7.25.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.0 - - '@babel/parser@7.25.8': - dependencies: - '@babel/types': 7.25.8 - - '@babel/plugin-transform-react-jsx-self@7.25.7(@babel/core@7.25.8)': - dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - - '@babel/plugin-transform-react-jsx-source@7.25.7(@babel/core@7.25.8)': - dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - - '@babel/template@7.25.7': - dependencies: - '@babel/code-frame': 7.25.7 - '@babel/parser': 7.25.8 - '@babel/types': 7.25.8 - - '@babel/traverse@7.25.7': - dependencies: - '@babel/code-frame': 7.25.7 - '@babel/generator': 7.25.7 - '@babel/parser': 7.25.8 - '@babel/template': 7.25.7 - '@babel/types': 7.25.8 - debug: 4.3.7 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.25.8': - dependencies: - '@babel/helper-string-parser': 7.25.7 - '@babel/helper-validator-identifier': 7.25.7 - to-fast-properties: 2.0.0 - - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - '@esbuild/aix-ppc64@0.21.5': optional: true @@ -1995,7 +1539,7 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@9.14.0(jiti@1.21.6))': + '@eslint-community/eslint-utils@4.4.1(eslint@9.14.0(jiti@1.21.6))': dependencies: eslint: 9.14.0(jiti@1.21.6) eslint-visitor-keys: 3.4.3 @@ -2047,18 +1591,6 @@ snapshots: '@humanwhocodes/retry@0.4.1': {} - '@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3)': - dependencies: - '@babel/core': 7.25.8 - '@babel/generator': 7.25.7 - '@babel/parser': 7.25.8 - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.8 - prettier: 3.3.3 - semver: 7.6.3 - transitivePeerDependencies: - - supports-color - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -2068,10 +1600,6 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -2089,11 +1617,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2109,150 +1632,112 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.11)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.11 - - '@radix-ui/react-slot@1.1.0(@types/react@18.3.11)(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.11 - - '@rollup/rollup-android-arm-eabi@4.24.0': + '@rollup/rollup-android-arm-eabi@4.25.0': optional: true - '@rollup/rollup-android-arm64@4.24.0': + '@rollup/rollup-android-arm64@4.25.0': optional: true - '@rollup/rollup-darwin-arm64@4.24.0': + '@rollup/rollup-darwin-arm64@4.25.0': optional: true - '@rollup/rollup-darwin-x64@4.24.0': + '@rollup/rollup-darwin-x64@4.25.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + '@rollup/rollup-freebsd-arm64@4.25.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.24.0': + '@rollup/rollup-freebsd-x64@4.25.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.24.0': + '@rollup/rollup-linux-arm-gnueabihf@4.25.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.24.0': + '@rollup/rollup-linux-arm-musleabihf@4.25.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + '@rollup/rollup-linux-arm64-gnu@4.25.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.24.0': + '@rollup/rollup-linux-arm64-musl@4.25.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.24.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.25.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.24.0': + '@rollup/rollup-linux-riscv64-gnu@4.25.0': optional: true - '@rollup/rollup-linux-x64-musl@4.24.0': + '@rollup/rollup-linux-s390x-gnu@4.25.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.24.0': + '@rollup/rollup-linux-x64-gnu@4.25.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.24.0': + '@rollup/rollup-linux-x64-musl@4.25.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.24.0': + '@rollup/rollup-win32-arm64-msvc@4.25.0': optional: true - '@sinclair/typebox@0.27.8': {} - - '@swc/core-darwin-arm64@1.7.36': + '@rollup/rollup-win32-ia32-msvc@4.25.0': optional: true - '@swc/core-darwin-x64@1.7.36': + '@rollup/rollup-win32-x64-msvc@4.25.0': optional: true - '@swc/core-linux-arm-gnueabihf@1.7.36': + '@swc/core-darwin-arm64@1.9.1': optional: true - '@swc/core-linux-arm64-gnu@1.7.36': + '@swc/core-darwin-x64@1.9.1': optional: true - '@swc/core-linux-arm64-musl@1.7.36': + '@swc/core-linux-arm-gnueabihf@1.9.1': optional: true - '@swc/core-linux-x64-gnu@1.7.36': + '@swc/core-linux-arm64-gnu@1.9.1': optional: true - '@swc/core-linux-x64-musl@1.7.36': + '@swc/core-linux-arm64-musl@1.9.1': optional: true - '@swc/core-win32-arm64-msvc@1.7.36': + '@swc/core-linux-x64-gnu@1.9.1': optional: true - '@swc/core-win32-ia32-msvc@1.7.36': + '@swc/core-linux-x64-musl@1.9.1': optional: true - '@swc/core-win32-x64-msvc@1.7.36': + '@swc/core-win32-arm64-msvc@1.9.1': optional: true - '@swc/core@1.7.36': + '@swc/core-win32-ia32-msvc@1.9.1': + optional: true + + '@swc/core-win32-x64-msvc@1.9.1': + optional: true + + '@swc/core@1.9.1': dependencies: '@swc/counter': 0.1.3 - '@swc/types': 0.1.13 + '@swc/types': 0.1.14 optionalDependencies: - '@swc/core-darwin-arm64': 1.7.36 - '@swc/core-darwin-x64': 1.7.36 - '@swc/core-linux-arm-gnueabihf': 1.7.36 - '@swc/core-linux-arm64-gnu': 1.7.36 - '@swc/core-linux-arm64-musl': 1.7.36 - '@swc/core-linux-x64-gnu': 1.7.36 - '@swc/core-linux-x64-musl': 1.7.36 - '@swc/core-win32-arm64-msvc': 1.7.36 - '@swc/core-win32-ia32-msvc': 1.7.36 - '@swc/core-win32-x64-msvc': 1.7.36 + '@swc/core-darwin-arm64': 1.9.1 + '@swc/core-darwin-x64': 1.9.1 + '@swc/core-linux-arm-gnueabihf': 1.9.1 + '@swc/core-linux-arm64-gnu': 1.9.1 + '@swc/core-linux-arm64-musl': 1.9.1 + '@swc/core-linux-x64-gnu': 1.9.1 + '@swc/core-linux-x64-musl': 1.9.1 + '@swc/core-win32-arm64-msvc': 1.9.1 + '@swc/core-win32-ia32-msvc': 1.9.1 + '@swc/core-win32-x64-msvc': 1.9.1 '@swc/counter@0.1.3': {} - '@swc/types@0.1.13': + '@swc/types@0.1.14': dependencies: '@swc/counter': 0.1.3 - '@tsconfig/node10@1.0.11': {} - - '@tsconfig/node12@1.0.11': {} - - '@tsconfig/node14@1.0.3': {} - - '@tsconfig/node16@1.0.4': {} - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.25.8 - '@babel/types': 7.25.8 - '@types/babel__generator': 7.6.8 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.6 - - '@types/babel__generator@7.6.8': - dependencies: - '@babel/types': 7.25.8 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.25.8 - '@babel/types': 7.25.8 - - '@types/babel__traverse@7.20.6': - dependencies: - '@babel/types': 7.25.8 - '@types/estree@1.0.6': {} '@types/json-schema@7.0.15': {} @@ -2265,63 +1750,145 @@ snapshots: '@types/react-dom@18.3.1': dependencies: - '@types/react': 18.3.11 + '@types/react': 18.3.12 - '@types/react@18.3.11': + '@types/react@18.3.12': dependencies: '@types/prop-types': 15.7.13 csstype: 3.1.3 - '@vitejs/plugin-react@4.3.2(vite@5.4.9(@types/node@22.9.0))': + '@typescript-eslint/eslint-plugin@8.13.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@babel/core': 7.25.8 - '@babel/plugin-transform-react-jsx-self': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-react-jsx-source': 7.25.7(@babel/core@7.25.8) - '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 5.4.9(@types/node@22.9.0) + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.13.0 + '@typescript-eslint/type-utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.13.0 + eslint: 9.14.0(jiti@1.21.6) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@vitest/expect@1.6.0': + '@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - chai: 4.5.0 + '@typescript-eslint/scope-manager': 8.13.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.13.0 + debug: 4.3.7 + eslint: 9.14.0(jiti@1.21.6) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color - '@vitest/runner@1.6.0': + '@typescript-eslint/scope-manager@8.13.0': dependencies: - '@vitest/utils': 1.6.0 - p-limit: 5.0.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/visitor-keys': 8.13.0 + + '@typescript-eslint/type-utils@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + debug: 4.3.7 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - eslint + - supports-color + + '@typescript-eslint/types@8.13.0': {} + + '@typescript-eslint/typescript-estree@8.13.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/visitor-keys': 8.13.0 + debug: 4.3.7 + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0(jiti@1.21.6)) + '@typescript-eslint/scope-manager': 8.13.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) + eslint: 9.14.0(jiti@1.21.6) + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@8.13.0': + dependencies: + '@typescript-eslint/types': 8.13.0 + eslint-visitor-keys: 3.4.3 + + '@vitejs/plugin-react-swc@3.7.1(vite@5.4.10(@types/node@22.9.0))': + dependencies: + '@swc/core': 1.9.1 + vite: 5.4.10(@types/node@22.9.0) + transitivePeerDependencies: + - '@swc/helpers' + + '@vitest/expect@2.1.4': + dependencies: + '@vitest/spy': 2.1.4 + '@vitest/utils': 2.1.4 + chai: 5.1.2 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.4(vite@5.4.10(@types/node@22.9.0))': + dependencies: + '@vitest/spy': 2.1.4 + estree-walker: 3.0.3 + magic-string: 0.30.12 + optionalDependencies: + vite: 5.4.10(@types/node@22.9.0) + + '@vitest/pretty-format@2.1.4': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.4': + dependencies: + '@vitest/utils': 2.1.4 pathe: 1.1.2 - '@vitest/snapshot@1.6.0': + '@vitest/snapshot@2.1.4': dependencies: + '@vitest/pretty-format': 2.1.4 magic-string: 0.30.12 pathe: 1.1.2 - pretty-format: 29.7.0 - '@vitest/spy@1.6.0': + '@vitest/spy@2.1.4': dependencies: - tinyspy: 2.2.1 + tinyspy: 3.0.2 - '@vitest/utils@1.6.0': + '@vitest/utils@2.1.4': dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + '@vitest/pretty-format': 2.1.4 + loupe: 3.1.2 + tinyrainbow: 1.2.0 acorn-jsx@5.3.2(acorn@8.14.0): dependencies: acorn: 8.14.0 - acorn-walk@8.3.4: - dependencies: - acorn: 8.12.1 - - acorn@8.12.1: {} - acorn@8.14.0: {} ajv@6.12.6: @@ -2335,16 +1902,10 @@ snapshots: ansi-regex@6.1.0: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} any-promise@1.3.0: {} @@ -2354,21 +1915,19 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - arg@4.1.3: {} - arg@5.0.2: {} argparse@2.0.1: {} - assertion-error@1.1.0: {} + assertion-error@2.0.1: {} autoprefixer@10.4.20(postcss@8.4.47): dependencies: - browserslist: 4.24.0 - caniuse-lite: 1.0.30001668 + browserslist: 4.24.2 + caniuse-lite: 1.0.30001679 fraction.js: 4.3.7 normalize-range: 0.1.2 - picocolors: 1.1.0 + picocolors: 1.1.1 postcss: 8.4.47 postcss-value-parser: 4.2.0 @@ -2389,12 +1948,12 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.24.0: + browserslist@4.24.2: dependencies: - caniuse-lite: 1.0.30001668 - electron-to-chromium: 1.5.39 + caniuse-lite: 1.0.30001679 + electron-to-chromium: 1.5.55 node-releases: 2.0.18 - update-browserslist-db: 1.1.1(browserslist@4.24.0) + update-browserslist-db: 1.1.1(browserslist@4.24.2) cac@6.7.14: {} @@ -2402,32 +1961,22 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001668: {} + caniuse-lite@1.0.30001679: {} - chai@4.5.0: + chai@5.1.2: dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.4 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.1.0 - - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - check-error@1.0.3: - dependencies: - get-func-name: 2.0.2 + check-error@2.1.1: {} chokidar@3.6.0: dependencies: @@ -2449,29 +1998,17 @@ snapshots: clsx@2.1.1: {} - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} commander@4.1.1: {} concat-map@0.0.1: {} - confbox@0.1.8: {} - - convert-source-map@2.0.0: {} - - create-require@1.1.1: {} - - cross-spawn@7.0.3: + cross-spawn@7.0.5: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -2485,23 +2022,17 @@ snapshots: dependencies: ms: 2.1.3 - deep-eql@4.1.4: - dependencies: - type-detect: 4.1.0 + deep-eql@5.0.2: {} deep-is@0.1.4: {} didyoumean@1.2.2: {} - diff-sequences@29.6.3: {} - - diff@4.0.2: {} - dlv@1.1.3: {} eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.39: {} + electron-to-chromium@1.5.55: {} emoji-regex@8.0.0: {} @@ -2535,10 +2066,16 @@ snapshots: escalade@3.2.0: {} - escape-string-regexp@1.0.5: {} - escape-string-regexp@4.0.0: {} + eslint-plugin-react-hooks@5.0.0(eslint@9.14.0(jiti@1.21.6)): + dependencies: + eslint: 9.14.0(jiti@1.21.6) + + eslint-plugin-react-refresh@0.4.14(eslint@9.14.0(jiti@1.21.6)): + dependencies: + eslint: 9.14.0(jiti@1.21.6) + eslint-scope@8.2.0: dependencies: esrecurse: 4.3.0 @@ -2550,7 +2087,7 @@ snapshots: eslint@9.14.0(jiti@1.21.6): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.14.0(jiti@1.21.6)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0(jiti@1.21.6)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.18.0 '@eslint/core': 0.7.0 @@ -2564,7 +2101,7 @@ snapshots: '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 debug: 4.3.7 escape-string-regexp: 4.0.0 eslint-scope: 8.2.0 @@ -2612,17 +2149,7 @@ snapshots: esutils@2.0.3: {} - execa@8.0.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 + expect-type@1.1.0: {} fast-deep-equal@3.1.3: {} @@ -2664,7 +2191,7 @@ snapshots: foreground-child@3.3.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 signal-exit: 4.1.0 fraction.js@4.3.7: {} @@ -2674,12 +2201,6 @@ snapshots: function-bind@1.1.2: {} - gensync@1.0.0-beta.2: {} - - get-func-name@2.0.2: {} - - get-stream@8.0.1: {} - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -2697,11 +2218,11 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - globals@11.12.0: {} - globals@14.0.0: {} - has-flag@3.0.0: {} + globals@15.12.0: {} + + graphemer@1.4.0: {} has-flag@4.0.0: {} @@ -2709,8 +2230,6 @@ snapshots: dependencies: function-bind: 1.1.2 - human-signals@5.0.0: {} - ignore@5.3.2: {} import-fresh@3.3.0: @@ -2738,8 +2257,6 @@ snapshots: is-number@7.0.0: {} - is-stream@3.0.0: {} - isexe@2.0.0: {} jackspeak@3.4.3: @@ -2752,24 +2269,16 @@ snapshots: js-tokens@4.0.0: {} - js-tokens@9.0.0: {} - js-yaml@4.1.0: dependencies: argparse: 2.0.1 - jsesc@3.0.2: {} - json-buffer@3.0.1: {} - json-parse-even-better-errors@3.0.2: {} - json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} - json5@2.2.3: {} - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -2785,11 +2294,6 @@ snapshots: lines-and-columns@1.2.4: {} - local-pkg@0.5.0: - dependencies: - mlly: 1.7.2 - pkg-types: 1.2.1 - locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -2800,16 +2304,10 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@2.3.7: - dependencies: - get-func-name: 2.0.2 + loupe@3.1.2: {} lru-cache@10.4.3: {} - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - lucide-react@0.456.0(react@18.3.1): dependencies: react: 18.3.1 @@ -2818,12 +2316,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - make-error@1.3.6: {} - - memorystream@0.3.1: {} - - merge-stream@2.0.0: {} - merge2@1.4.1: {} micromatch@4.0.8: @@ -2831,8 +2323,6 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mimic-fn@4.0.0: {} - minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -2843,13 +2333,6 @@ snapshots: minipass@7.1.2: {} - mlly@1.7.2: - dependencies: - acorn: 8.12.1 - pathe: 1.1.2 - pkg-types: 1.2.1 - ufo: 1.5.4 - ms@2.1.3: {} mz@2.7.0: @@ -2868,30 +2351,10 @@ snapshots: normalize-range@0.1.2: {} - npm-normalize-package-bin@3.0.1: {} - - npm-run-all2@6.2.3: - dependencies: - ansi-styles: 6.2.1 - cross-spawn: 7.0.3 - memorystream: 0.3.1 - minimatch: 9.0.5 - pidtree: 0.6.0 - read-package-json-fast: 3.0.2 - shell-quote: 1.8.1 - - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - object-assign@4.1.1: {} object-hash@3.0.0: {} - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2905,10 +2368,6 @@ snapshots: dependencies: yocto-queue: 0.1.0 - p-limit@5.0.0: - dependencies: - yocto-queue: 1.1.1 - p-locate@5.0.0: dependencies: p-limit: 3.1.0 @@ -2923,8 +2382,6 @@ snapshots: path-key@3.1.1: {} - path-key@4.0.0: {} - path-parse@1.0.7: {} path-scurry@1.11.1: @@ -2934,24 +2391,16 @@ snapshots: pathe@1.1.2: {} - pathval@1.1.1: {} + pathval@2.0.0: {} - picocolors@1.1.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} - pidtree@0.6.0: {} - pify@2.3.0: {} pirates@4.0.6: {} - pkg-types@1.2.1: - dependencies: - confbox: 0.1.8 - mlly: 1.7.2 - pathe: 1.1.2 - postcss-import@15.1.0(postcss@8.4.47): dependencies: postcss: 8.4.47 @@ -2964,13 +2413,12 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.4.47 - postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3)): + postcss-load-config@4.0.2(postcss@8.4.47): dependencies: lilconfig: 3.1.2 yaml: 2.6.0 optionalDependencies: postcss: 8.4.47 - ts-node: 10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3) postcss-nested@6.2.0(postcss@8.4.47): dependencies: @@ -2987,25 +2435,11 @@ snapshots: postcss@8.4.47: dependencies: nanoid: 3.3.7 - picocolors: 1.1.0 + picocolors: 1.1.1 source-map-js: 1.2.1 prelude-ls@1.2.1: {} - prettier-plugin-tailwindcss@0.6.8(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3))(prettier@3.3.3): - dependencies: - prettier: 3.3.3 - optionalDependencies: - '@ianvs/prettier-plugin-sort-imports': 4.3.1(prettier@3.3.3) - - prettier@3.3.3: {} - - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -3016,10 +2450,6 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-is@18.3.1: {} - - react-refresh@0.14.2: {} - react@18.3.1: dependencies: loose-envify: 1.4.0 @@ -3028,11 +2458,6 @@ snapshots: dependencies: pify: 2.3.0 - read-package-json-fast@3.0.2: - dependencies: - json-parse-even-better-errors: 3.0.2 - npm-normalize-package-bin: 3.0.1 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -3047,26 +2472,28 @@ snapshots: reusify@1.0.4: {} - rollup@4.24.0: + rollup@4.25.0: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.24.0 - '@rollup/rollup-android-arm64': 4.24.0 - '@rollup/rollup-darwin-arm64': 4.24.0 - '@rollup/rollup-darwin-x64': 4.24.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 - '@rollup/rollup-linux-arm-musleabihf': 4.24.0 - '@rollup/rollup-linux-arm64-gnu': 4.24.0 - '@rollup/rollup-linux-arm64-musl': 4.24.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 - '@rollup/rollup-linux-riscv64-gnu': 4.24.0 - '@rollup/rollup-linux-s390x-gnu': 4.24.0 - '@rollup/rollup-linux-x64-gnu': 4.24.0 - '@rollup/rollup-linux-x64-musl': 4.24.0 - '@rollup/rollup-win32-arm64-msvc': 4.24.0 - '@rollup/rollup-win32-ia32-msvc': 4.24.0 - '@rollup/rollup-win32-x64-msvc': 4.24.0 + '@rollup/rollup-android-arm-eabi': 4.25.0 + '@rollup/rollup-android-arm64': 4.25.0 + '@rollup/rollup-darwin-arm64': 4.25.0 + '@rollup/rollup-darwin-x64': 4.25.0 + '@rollup/rollup-freebsd-arm64': 4.25.0 + '@rollup/rollup-freebsd-x64': 4.25.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.25.0 + '@rollup/rollup-linux-arm-musleabihf': 4.25.0 + '@rollup/rollup-linux-arm64-gnu': 4.25.0 + '@rollup/rollup-linux-arm64-musl': 4.25.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.25.0 + '@rollup/rollup-linux-riscv64-gnu': 4.25.0 + '@rollup/rollup-linux-s390x-gnu': 4.25.0 + '@rollup/rollup-linux-x64-gnu': 4.25.0 + '@rollup/rollup-linux-x64-musl': 4.25.0 + '@rollup/rollup-win32-arm64-msvc': 4.25.0 + '@rollup/rollup-win32-ia32-msvc': 4.25.0 + '@rollup/rollup-win32-x64-msvc': 4.25.0 fsevents: 2.3.3 run-parallel@1.2.0: @@ -3077,8 +2504,6 @@ snapshots: dependencies: loose-envify: 1.4.0 - semver@6.3.1: {} - semver@7.6.3: {} shebang-command@2.0.0: @@ -3087,8 +2512,6 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.1: {} - siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -3097,7 +2520,7 @@ snapshots: stackback@0.0.2: {} - std-env@3.7.0: {} + std-env@3.8.0: {} string-width@4.2.3: dependencies: @@ -3119,14 +2542,8 @@ snapshots: dependencies: ansi-regex: 6.1.0 - strip-final-newline@3.0.0: {} - strip-json-comments@3.1.1: {} - strip-literal@2.1.0: - dependencies: - js-tokens: 9.0.0 - sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -3137,10 +2554,6 @@ snapshots: pirates: 4.0.6 ts-interface-checker: 0.1.13 - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -3149,11 +2562,11 @@ snapshots: tailwind-merge@2.5.4: {} - tailwindcss-animate@1.0.7(tailwindcss@3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.14): dependencies: - tailwindcss: 3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3)) + tailwindcss: 3.4.14 - tailwindcss@3.4.14(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3)): + tailwindcss@3.4.14: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -3168,11 +2581,11 @@ snapshots: micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 - picocolors: 1.1.0 + picocolors: 1.1.1 postcss: 8.4.47 postcss-import: 15.1.0(postcss@8.4.47) postcss-js: 4.0.1(postcss@8.4.47) - postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3)) + postcss-load-config: 4.0.2(postcss@8.4.47) postcss-nested: 6.2.0(postcss@8.4.47) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -3192,55 +2605,48 @@ snapshots: tinybench@2.9.0: {} - tinypool@0.8.4: {} + tinyexec@0.3.1: {} - tinyspy@2.2.1: {} + tinypool@1.0.1: {} - to-fast-properties@2.0.0: {} + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - ts-interface-checker@0.1.13: {} - - ts-node@10.9.2(@swc/core@1.7.36)(@types/node@22.9.0)(typescript@5.6.3): + ts-api-utils@1.4.0(typescript@5.6.3): dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.11 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 22.9.0 - acorn: 8.12.1 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 typescript: 5.6.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - optionalDependencies: - '@swc/core': 1.7.36 + + ts-interface-checker@0.1.13: {} type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - type-detect@4.1.0: {} + typescript-eslint@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.13.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/parser': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - eslint + - supports-color typescript@5.6.3: {} - ufo@1.5.4: {} - undici-types@6.19.8: {} - update-browserslist-db@1.1.1(browserslist@4.24.0): + update-browserslist-db@1.1.1(browserslist@4.24.2): dependencies: - browserslist: 4.24.0 + browserslist: 4.24.2 escalade: 3.2.0 - picocolors: 1.1.0 + picocolors: 1.1.1 uri-js@4.4.1: dependencies: @@ -3248,15 +2654,12 @@ snapshots: util-deprecate@1.0.2: {} - v8-compile-cache-lib@3.0.1: {} - - vite-node@1.6.0(@types/node@22.9.0): + vite-node@2.1.4(@types/node@22.9.0): dependencies: cac: 6.7.14 debug: 4.3.7 pathe: 1.1.2 - picocolors: 1.1.0 - vite: 5.4.9(@types/node@22.9.0) + vite: 5.4.10(@types/node@22.9.0) transitivePeerDependencies: - '@types/node' - less @@ -3268,42 +2671,43 @@ snapshots: - supports-color - terser - vite@5.4.9(@types/node@22.9.0): + vite@5.4.10(@types/node@22.9.0): dependencies: esbuild: 0.21.5 postcss: 8.4.47 - rollup: 4.24.0 + rollup: 4.25.0 optionalDependencies: '@types/node': 22.9.0 fsevents: 2.3.3 - vitest@1.6.0(@types/node@22.9.0): + vitest@2.1.4(@types/node@22.9.0): dependencies: - '@vitest/expect': 1.6.0 - '@vitest/runner': 1.6.0 - '@vitest/snapshot': 1.6.0 - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - acorn-walk: 8.3.4 - chai: 4.5.0 + '@vitest/expect': 2.1.4 + '@vitest/mocker': 2.1.4(vite@5.4.10(@types/node@22.9.0)) + '@vitest/pretty-format': 2.1.4 + '@vitest/runner': 2.1.4 + '@vitest/snapshot': 2.1.4 + '@vitest/spy': 2.1.4 + '@vitest/utils': 2.1.4 + chai: 5.1.2 debug: 4.3.7 - execa: 8.0.1 - local-pkg: 0.5.0 + expect-type: 1.1.0 magic-string: 0.30.12 pathe: 1.1.2 - picocolors: 1.1.0 - std-env: 3.7.0 - strip-literal: 2.1.0 + std-env: 3.8.0 tinybench: 2.9.0 - tinypool: 0.8.4 - vite: 5.4.9(@types/node@22.9.0) - vite-node: 1.6.0(@types/node@22.9.0) + tinyexec: 0.3.1 + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.4.10(@types/node@22.9.0) + vite-node: 2.1.4(@types/node@22.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.9.0 transitivePeerDependencies: - less - lightningcss + - msw - sass - sass-embedded - stylus @@ -3334,12 +2738,6 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 - yallist@3.1.1: {} - yaml@2.6.0: {} - yn@3.1.1: {} - yocto-queue@0.1.0: {} - - yocto-queue@1.1.1: {} diff --git a/frontend/postcss.config.cjs b/frontend/postcss.config.js similarity index 73% rename from frontend/postcss.config.cjs rename to frontend/postcss.config.js index 4df9d3b..2e7af2b 100644 --- a/frontend/postcss.config.cjs +++ b/frontend/postcss.config.js @@ -1,6 +1,6 @@ -module.exports = { +export default { plugins: { - autoprefixer: {}, tailwindcss: {}, + autoprefixer: {}, }, -}; +} diff --git a/frontend/src/App.css b/frontend/src/App.css index df9d556..b9d355d 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,26 +1,42 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { - --background-color: #fff; - --text-color: #111; +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; } -@media (prefers-color-scheme: dark) { - :root { - --background-color: rgb(20, 20, 20); - --text-color: rgb(230, 230, 230); +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); } } -body { - background-color: var(--background-color); - color: var(--text-color); -} - -@layer base { - :root { - --radius: 0.5rem; +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; } } + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a5bd609..323b579 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,91 +1,33 @@ -import { useEffect, useState } from 'react'; +import { useState } from "react"; +import "./App.css"; -const backendUrl = import.meta.env.PROD - ? '/api' - : `http://${import.meta.env.VITE_BACKEND_TARGET}/api`; - -const Code = (props: JSX.IntrinsicElements['code']) => ( - -); - -type SeenIP = { - last_seen: string; - ip: string; - count: number; -}; - -export default function App() { - const [seenIps, setSeenIps] = useState([]); - const [error, setError] = useState(null); - - const refreshData = async () => { - try { - const response = await fetch(`${backendUrl}/ips`); - if (!response.ok) { - throw new Error(`Error: ${response.statusText}`); - } - const data = await response.json(); - - setSeenIps(data.ips); - setError(null); // Clear any previous errors - console.log('Data fetched:', data); - } catch (error) { - console.error('Error fetching data:', error); - setError(error.message); - } - }; - - // Refresh data on component mount and every 30 seconds - useEffect(() => { - refreshData(); - - const interval = setInterval( - () => { - refreshData(); - }, - (import.meta.env.DEV ? 3 : 30) * 1000, - ); - - return () => clearInterval(interval); - }, []); +function App() { + const [count, setCount] = useState(0); return ( -
-
-

LinkPulse

- - {error && ( -
- {error} -
- )} - -
- - - {error == null - ? seenIps.map((ip) => ( - - - - - - )) - : null} - -
- {ip.ip} - - {ip.count} time{ip.count > 1 ? 's' : ''} - {ip.last_seen}
-
+ <> + -
+

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ ); } + +export default App; diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..9de0433 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,137 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 10% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + } + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx deleted file mode 100644 index a4e3e6d..0000000 --- a/frontend/src/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import App from './App.tsx'; -import './App.css'; -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; - -createRoot(document.getElementById('root')!).render( - - - , -); diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs deleted file mode 100644 index 612174a..0000000 --- a/frontend/tailwind.config.cjs +++ /dev/null @@ -1,20 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ['./src/**/*.{js,jsx,ts,tsx}', './*.html'], - darkMode: ['media', 'class'], - mode: 'jit', - theme: { - extend: { - fontFamily: { - inter: ['Inter', 'sans-serif'] - }, - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' - }, - colors: {} - } - }, - plugins: [require("tailwindcss-animate")] -}; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..e095f2e --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,57 @@ +/** @type {import('tailwindcss').Config} */ +export default { + darkMode: ["class"], + content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"], + theme: { + extend: { + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + } + } + } + }, + plugins: [require("tailwindcss-animate")], +}; diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..b6d4c6a --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index d50276b..fec8c8e 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,37 +1,13 @@ { + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ], "compilerOptions": { - "allowJs": true, - "allowImportingTsExtensions": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "incremental": true, - "isolatedModules": true, - "jsx": "preserve", - "lib": ["dom", "dom.iterable", "esnext"], - "module": "nodenext", - "moduleResolution": "nodenext", - "noEmit": true, - "noImplicitOverride": true, - "noUnusedLocals": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "strict": true, - "target": "es2017", - "types": ["vite/client"], "baseUrl": ".", "paths": { "@/*": ["./src/*"] } - }, - "exclude": ["node_modules"], - "include": ["**/*.ts", "**/*.tsx"], - "ts-node": { - "transpileOnly": true, - "transpiler": "ts-node/transpilers/swc", - "files": true, - "compilerOptions": { - "module": "esnext", - "isolatedModules": false - } } } diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..a1d6d41 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 78caf99..a625d2c 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,12 +1,13 @@ -import path from 'path'; -import react from '@vitejs/plugin-react'; -import { defineConfig } from 'vite'; +import path from 'path' +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' +// https://vite.dev/config/ export default defineConfig({ plugins: [react()], resolve: { alias: { - '@': path.resolve(__dirname, './src'), + "@": path.resolve(__dirname, "./src"), }, }, -}); +}) From 3d009700032e228b96ed5d3b62fa46c917706caf Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 14:34:55 -0600 Subject: [PATCH 081/100] Setup React @tanstack/router, copy shadcn Authentication page --- .vscode/settings.json | 1 + frontend/package.json | 6 + frontend/pnpm-lock.yaml | 838 ++++++++++++++++++ frontend/src/components/auth/UserAuthForm.tsx | 71 ++ frontend/src/components/icons.tsx | 148 ++++ frontend/src/components/ui/button.tsx | 14 +- frontend/src/components/ui/input.tsx | 22 + frontend/src/components/ui/label.tsx | 24 + frontend/src/main.tsx | 34 +- frontend/src/routeTree.gen.ts | 116 +++ frontend/src/routes/__root.tsx | 11 + frontend/src/routes/index.lazy.tsx | 13 + frontend/src/routes/login.lazy.tsx | 101 +++ frontend/vite.config.ts | 6 +- 14 files changed, 1388 insertions(+), 17 deletions(-) create mode 100644 frontend/src/components/auth/UserAuthForm.tsx create mode 100644 frontend/src/components/icons.tsx create mode 100644 frontend/src/components/ui/input.tsx create mode 100644 frontend/src/components/ui/label.tsx create mode 100644 frontend/src/routeTree.gen.ts create mode 100644 frontend/src/routes/__root.tsx create mode 100644 frontend/src/routes/index.lazy.tsx create mode 100644 frontend/src/routes/login.lazy.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 1d90d09..81ebc80 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -32,6 +32,7 @@ "vitest" ], "python.analysis.extraPaths": ["./backend/"], + "editor.formatOnSave": true, "[github-actions-workflow]": { "editor.formatOnSave": false }, diff --git a/frontend/package.json b/frontend/package.json index 8d099e4..5ddd6e5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,9 @@ "preview": "vite preview" }, "dependencies": { + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-slot": "^1.1.0", + "@tanstack/react-router": "^1.81.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-react": "^0.456.0", @@ -25,6 +28,8 @@ }, "devDependencies": { "@eslint/js": "^9.13.0", + "@tanstack/router-devtools": "^1.81.1", + "@tanstack/router-plugin": "^1.79.0", "@types/node": "^22.9.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", @@ -35,6 +40,7 @@ "eslint-plugin-react-refresh": "^0.4.14", "globals": "^15.11.0", "postcss": "^8.4.47", + "prettier": "^3.3.3", "tailwindcss": "^3.4.14", "typescript": "~5.6.2", "typescript-eslint": "^8.11.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 8aac54a..b06b89e 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,6 +8,15 @@ importers: .: dependencies: + '@radix-ui/react-label': + specifier: ^2.1.0 + version: 2.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': + specifier: ^1.1.0 + version: 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@tanstack/react-router': + specifier: ^1.81.1 + version: 1.81.1(@tanstack/router-generator@1.79.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -33,6 +42,12 @@ importers: '@eslint/js': specifier: ^9.13.0 version: 9.14.0 + '@tanstack/router-devtools': + specifier: ^1.81.1 + version: 1.81.1(@tanstack/react-router@1.81.1(@tanstack/router-generator@1.79.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/router-plugin': + specifier: ^1.79.0 + version: 1.79.0(vite@5.4.10(@types/node@22.9.0)) '@types/node': specifier: ^22.9.0 version: 22.9.0 @@ -63,6 +78,9 @@ importers: postcss: specifier: ^8.4.47 version: 8.4.47 + prettier: + specifier: ^3.3.3 + version: 3.3.3 tailwindcss: specifier: ^3.4.14 version: 3.4.14 @@ -85,144 +103,371 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.2': + resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.2': + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.25.9': + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.25.9': + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.0': + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.25.9': + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.25.9': + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.25.9': + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.0': + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + engines: {node: '>=6.9.0'} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.21.5': resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -315,6 +560,50 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@radix-ui/react-compose-refs@1.1.0': + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.0': + resolution: {integrity: sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.0.0': + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.1.0': + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@rollup/rollup-android-arm-eabi@4.25.0': resolution: {integrity: sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==} cpu: [arm] @@ -480,6 +769,73 @@ packages: '@swc/types@0.1.14': resolution: {integrity: sha512-PbSmTiYCN+GMrvfjrMo9bdY+f2COnwbdnoMw7rqU/PI5jXpKjxOGZ0qqZCImxnT81NkNsKnmEpvu+hRXLBeCJg==} + '@tanstack/history@1.61.1': + resolution: {integrity: sha512-2CqERleeqO3hkhJmyJm37tiL3LYgeOpmo8szqdjgtnnG0z7ZpvzkZz6HkfOr9Ca/ha7mhAiouSvLYuLkM37AMg==} + engines: {node: '>=12'} + + '@tanstack/react-router@1.81.1': + resolution: {integrity: sha512-2MufdPrN2fwcmf9r82Egl7RUzNN/tlVUz97TphCDX3k5yMslwouKLw481mY26PksiHw1nIYfsAggGTgSELP1xg==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/router-generator': 1.79.0 + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + '@tanstack/router-generator': + optional: true + + '@tanstack/react-store@0.5.6': + resolution: {integrity: sha512-SitIpS5jTj28DajjLpWbIX+YetmJL+6PRY0DKKiCGBKfYIqj3ryODQYF3jB3SNoR9ifUA/jFkqbJdBKFtWd+AQ==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + + '@tanstack/router-devtools@1.81.1': + resolution: {integrity: sha512-gSAqQ6/+fycHxj1Yfsfg5OPZ8KMDlcXh5rb/tHzlOpC+zGtvxQAR6KSes4kmIQ2HD0C094tW9+Nkrfl7/uYmlQ==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/react-router': ^1.81.1 + react: '>=18' + react-dom: '>=18' + + '@tanstack/router-generator@1.79.0': + resolution: {integrity: sha512-HJxmYs7GAA1AJQzyfy4Hiygmg93qCCDiAxQ//zCRMbzVntwpqtZ96o9UGOPjT3Lw0SxbyzbKgpo3zqCdwlv8Ew==} + engines: {node: '>=12'} + + '@tanstack/router-plugin@1.79.0': + resolution: {integrity: sha512-dY81YyKxON9KhZQlrkkuxsl688pGpZ4HAF5w40ZkJa+nwmEJdg0b2td+MPXWbtmSd1t1cbYlFvc68k+PUSHN/A==} + engines: {node: '>=12'} + peerDependencies: + '@rsbuild/core': '>=1.0.2' + vite: '>=5.0.0' + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + vite: + optional: true + webpack: + optional: true + + '@tanstack/store@0.5.5': + resolution: {integrity: sha512-EOSrgdDAJExbvRZEQ/Xhh9iZchXpMN+ga1Bnk8Nmygzs8TfiE6hbzThF+Pr2G19uHL6+DTDTHhJ8VQiOd7l4tA==} + + '@tanstack/virtual-file-routes@1.64.0': + resolution: {integrity: sha512-soW+gE9QTmMaqXM17r7y1p8NiQVIIECjdTaYla8BKL5Flj030m3KuxEQoiG1XgjtA0O7ayznFz2YvPcXIy3qDg==} + engines: {node: '>=12'} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -642,6 +998,9 @@ packages: peerDependencies: postcss: ^8.1.0 + babel-dead-code-elimination@1.0.6: + resolution: {integrity: sha512-JxFi9qyRJpN0LjEbbjbN8g0ux71Qppn9R8Qe3k6QzHg2CaKsbUQtbn307LQGiDLGjV6JCtEFqfxzVig9MyDCHQ==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -720,6 +1079,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cross-spawn@7.0.5: resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} engines: {node: '>= 8'} @@ -771,6 +1133,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -889,6 +1256,13 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -901,6 +1275,10 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -909,6 +1287,11 @@ packages: resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==} engines: {node: '>=18'} + goober@2.1.16: + resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==} + peerDependencies: + csstype: ^3.0.10 + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -973,6 +1356,11 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -982,6 +1370,11 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1017,6 +1410,9 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.456.0: resolution: {integrity: sha512-DIIGJqTT5X05sbAsQ+OhA8OtJYyD4NsEMCA/HQW/Y6ToPQ7gwbtujIoeAaup4HpHzV35SQOarKAWH8LYglB6eA==} peerDependencies: @@ -1178,6 +1574,11 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1205,6 +1606,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -1224,6 +1628,10 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} @@ -1310,6 +1718,12 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1341,6 +1755,11 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tsx@4.19.2: + resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1362,6 +1781,15 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + unplugin@1.15.0: + resolution: {integrity: sha512-jTPIs63W+DUEDW207ztbaoO7cQ4p5aVaB823LSlxpsFEU3Mykwxf3ZGC/wzxFJeZlASZYgVrWeo7LgOrqJZ8RA==} + engines: {node: '>=14.0.0'} + peerDependencies: + webpack-sources: ^3 + peerDependenciesMeta: + webpack-sources: + optional: true + update-browserslist-db@1.1.1: resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true @@ -1371,6 +1799,11 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.2.2: + resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -1435,6 +1868,9 @@ packages: jsdom: optional: true + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1457,6 +1893,9 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@2.6.0: resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} engines: {node: '>= 14'} @@ -1466,79 +1905,269 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + snapshots: '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.2': {} + + '@babel/core@7.26.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + convert-source-map: 2.0.0 + debug: 4.3.7 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.26.2': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + + '@babel/helper-compilation-targets@7.25.9': + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.25.9': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.26.0': + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + + '@babel/parser@7.26.2': + dependencies: + '@babel/types': 7.26.0 + + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@babel/traverse@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + debug: 4.3.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@esbuild/aix-ppc64@0.21.5': optional: true + '@esbuild/aix-ppc64@0.23.1': + optional: true + '@esbuild/android-arm64@0.21.5': optional: true + '@esbuild/android-arm64@0.23.1': + optional: true + '@esbuild/android-arm@0.21.5': optional: true + '@esbuild/android-arm@0.23.1': + optional: true + '@esbuild/android-x64@0.21.5': optional: true + '@esbuild/android-x64@0.23.1': + optional: true + '@esbuild/darwin-arm64@0.21.5': optional: true + '@esbuild/darwin-arm64@0.23.1': + optional: true + '@esbuild/darwin-x64@0.21.5': optional: true + '@esbuild/darwin-x64@0.23.1': + optional: true + '@esbuild/freebsd-arm64@0.21.5': optional: true + '@esbuild/freebsd-arm64@0.23.1': + optional: true + '@esbuild/freebsd-x64@0.21.5': optional: true + '@esbuild/freebsd-x64@0.23.1': + optional: true + '@esbuild/linux-arm64@0.21.5': optional: true + '@esbuild/linux-arm64@0.23.1': + optional: true + '@esbuild/linux-arm@0.21.5': optional: true + '@esbuild/linux-arm@0.23.1': + optional: true + '@esbuild/linux-ia32@0.21.5': optional: true + '@esbuild/linux-ia32@0.23.1': + optional: true + '@esbuild/linux-loong64@0.21.5': optional: true + '@esbuild/linux-loong64@0.23.1': + optional: true + '@esbuild/linux-mips64el@0.21.5': optional: true + '@esbuild/linux-mips64el@0.23.1': + optional: true + '@esbuild/linux-ppc64@0.21.5': optional: true + '@esbuild/linux-ppc64@0.23.1': + optional: true + '@esbuild/linux-riscv64@0.21.5': optional: true + '@esbuild/linux-riscv64@0.23.1': + optional: true + '@esbuild/linux-s390x@0.21.5': optional: true + '@esbuild/linux-s390x@0.23.1': + optional: true + '@esbuild/linux-x64@0.21.5': optional: true + '@esbuild/linux-x64@0.23.1': + optional: true + '@esbuild/netbsd-x64@0.21.5': optional: true + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + '@esbuild/openbsd-x64@0.21.5': optional: true + '@esbuild/openbsd-x64@0.23.1': + optional: true + '@esbuild/sunos-x64@0.21.5': optional: true + '@esbuild/sunos-x64@0.23.1': + optional: true + '@esbuild/win32-arm64@0.21.5': optional: true + '@esbuild/win32-arm64@0.23.1': + optional: true + '@esbuild/win32-ia32@0.21.5': optional: true + '@esbuild/win32-ia32@0.23.1': + optional: true + '@esbuild/win32-x64@0.21.5': optional: true + '@esbuild/win32-x64@0.23.1': + optional: true + '@eslint-community/eslint-utils@4.4.1(eslint@9.14.0(jiti@1.21.6))': dependencies: eslint: 9.14.0(jiti@1.21.6) @@ -1632,6 +2261,37 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.12)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@radix-ui/react-label@2.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + + '@radix-ui/react-slot@1.1.0(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@rollup/rollup-android-arm-eabi@4.25.0': optional: true @@ -1738,6 +2398,94 @@ snapshots: dependencies: '@swc/counter': 0.1.3 + '@tanstack/history@1.61.1': {} + + '@tanstack/react-router@1.81.1(@tanstack/router-generator@1.79.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/history': 1.61.1 + '@tanstack/react-store': 0.5.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + optionalDependencies: + '@tanstack/router-generator': 1.79.0 + + '@tanstack/react-store@0.5.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/store': 0.5.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-sync-external-store: 1.2.2(react@18.3.1) + + '@tanstack/router-devtools@1.81.1(@tanstack/react-router@1.81.1(@tanstack/router-generator@1.79.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/react-router': 1.81.1(@tanstack/router-generator@1.79.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + clsx: 2.1.1 + goober: 2.1.16(csstype@3.1.3) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - csstype + + '@tanstack/router-generator@1.79.0': + dependencies: + '@tanstack/virtual-file-routes': 1.64.0 + prettier: 3.3.3 + tsx: 4.19.2 + zod: 3.23.8 + + '@tanstack/router-plugin@1.79.0(vite@5.4.10(@types/node@22.9.0))': + dependencies: + '@babel/core': 7.26.0 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) + '@babel/template': 7.25.9 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + '@tanstack/router-generator': 1.79.0 + '@tanstack/virtual-file-routes': 1.64.0 + '@types/babel__core': 7.20.5 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + babel-dead-code-elimination: 1.0.6 + chokidar: 3.6.0 + unplugin: 1.15.0 + zod: 3.23.8 + optionalDependencies: + vite: 5.4.10(@types/node@22.9.0) + transitivePeerDependencies: + - supports-color + - webpack-sources + + '@tanstack/store@0.5.5': {} + + '@tanstack/virtual-file-routes@1.64.0': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.26.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.26.0 + '@types/estree@1.0.6': {} '@types/json-schema@7.0.15': {} @@ -1931,6 +2679,15 @@ snapshots: postcss: 8.4.47 postcss-value-parser: 4.2.0 + babel-dead-code-elimination@1.0.6: + dependencies: + '@babel/core': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + balanced-match@1.0.2: {} binary-extensions@2.3.0: {} @@ -2008,6 +2765,8 @@ snapshots: concat-map@0.0.1: {} + convert-source-map@2.0.0: {} + cross-spawn@7.0.5: dependencies: path-key: 3.1.1 @@ -2064,6 +2823,33 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -2201,6 +2987,12 @@ snapshots: function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} + + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -2218,10 +3010,16 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + globals@11.12.0: {} + globals@14.0.0: {} globals@15.12.0: {} + goober@2.1.16(csstype@3.1.3): + dependencies: + csstype: 3.1.3 + graphemer@1.4.0: {} has-flag@4.0.0: {} @@ -2273,12 +3071,16 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.0.2: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -2308,6 +3110,10 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lucide-react@0.456.0(react@18.3.1): dependencies: react: 18.3.1 @@ -2440,6 +3246,8 @@ snapshots: prelude-ls@1.2.1: {} + prettier@3.3.3: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -2464,6 +3272,8 @@ snapshots: resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.8: dependencies: is-core-module: 2.15.1 @@ -2504,6 +3314,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + semver@6.3.1: {} + semver@7.6.3: {} shebang-command@2.0.0: @@ -2603,6 +3415,10 @@ snapshots: dependencies: any-promise: 1.3.0 + tiny-invariant@1.3.3: {} + + tiny-warning@1.0.3: {} + tinybench@2.9.0: {} tinyexec@0.3.1: {} @@ -2623,6 +3439,13 @@ snapshots: ts-interface-checker@0.1.13: {} + tsx@4.19.2: + dependencies: + esbuild: 0.23.1 + get-tsconfig: 4.8.1 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -2642,6 +3465,11 @@ snapshots: undici-types@6.19.8: {} + unplugin@1.15.0: + dependencies: + acorn: 8.14.0 + webpack-virtual-modules: 0.6.2 + update-browserslist-db@1.1.1(browserslist@4.24.2): dependencies: browserslist: 4.24.2 @@ -2652,6 +3480,10 @@ snapshots: dependencies: punycode: 2.3.1 + use-sync-external-store@1.2.2(react@18.3.1): + dependencies: + react: 18.3.1 + util-deprecate@1.0.2: {} vite-node@2.1.4(@types/node@22.9.0): @@ -2715,6 +3547,8 @@ snapshots: - supports-color - terser + webpack-virtual-modules@0.6.2: {} + which@2.0.2: dependencies: isexe: 2.0.0 @@ -2738,6 +3572,10 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + yallist@3.1.1: {} + yaml@2.6.0: {} yocto-queue@0.1.0: {} + + zod@3.23.8: {} diff --git a/frontend/src/components/auth/UserAuthForm.tsx b/frontend/src/components/auth/UserAuthForm.tsx new file mode 100644 index 0000000..7b46c72 --- /dev/null +++ b/frontend/src/components/auth/UserAuthForm.tsx @@ -0,0 +1,71 @@ +"use client"; + +import * as React from "react"; + +import { cn } from "@/lib/utils"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Icons } from "@/components/icons"; + +interface UserAuthFormProps extends React.HTMLAttributes {} + +export function UserAuthForm({ className, ...props }: UserAuthFormProps) { + const [isLoading, setIsLoading] = React.useState(false); + + async function onSubmit(event: React.SyntheticEvent) { + event.preventDefault(); + setIsLoading(true); + + setTimeout(() => { + setIsLoading(false); + }, 3000); + } + + return ( +
+
+
+
+ + +
+ +
+
+
+
+ +
+
+ + Or continue with + +
+
+ +
+ ); +} diff --git a/frontend/src/components/icons.tsx b/frontend/src/components/icons.tsx new file mode 100644 index 0000000..97ebf25 --- /dev/null +++ b/frontend/src/components/icons.tsx @@ -0,0 +1,148 @@ +type IconProps = React.HTMLAttributes + +export const Icons = { + logo: (props: IconProps) => ( + + + + + + ), + twitter: (props: IconProps) => ( + + + + ), + gitHub: (props: IconProps) => ( + + + + ), + radix: (props: IconProps) => ( + + + + + + ), + aria: (props: IconProps) => ( + + + + ), + npm: (props: IconProps) => ( + + + + ), + yarn: (props: IconProps) => ( + + + + ), + pnpm: (props: IconProps) => ( + + + + ), + react: (props: IconProps) => ( + + + + ), + tailwind: (props: IconProps) => ( + + + + ), + google: (props: IconProps) => ( + + + + ), + apple: (props: IconProps) => ( + + + + ), + paypal: (props: IconProps) => ( + + + + ), + spinner: (props: IconProps) => ( + + + + ), +} \ No newline at end of file diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index 4f52e23..65d4fcd 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -5,20 +5,20 @@ import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-zinc-950 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 dark:focus-visible:ring-zinc-300", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { default: - "bg-zinc-900 text-zinc-50 shadow hover:bg-zinc-900/90 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/90", + "bg-primary text-primary-foreground shadow hover:bg-primary/90", destructive: - "bg-red-500 text-zinc-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/90", + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", outline: - "border border-zinc-200 bg-white shadow-sm hover:bg-zinc-100 hover:text-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-800 dark:hover:text-zinc-50", + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", secondary: - "bg-zinc-100 text-zinc-900 shadow-sm hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80", - ghost: "hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800 dark:hover:text-zinc-50", - link: "text-zinc-900 underline-offset-4 hover:underline dark:text-zinc-50", + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2", diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx new file mode 100644 index 0000000..69b64fb --- /dev/null +++ b/frontend/src/components/ui/input.tsx @@ -0,0 +1,22 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/frontend/src/components/ui/label.tsx b/frontend/src/components/ui/label.tsx new file mode 100644 index 0000000..683faa7 --- /dev/null +++ b/frontend/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index bef5202..d819966 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,28 @@ +import { RouterProvider, createRouter } from '@tanstack/react-router' import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' +import ReactDOM from 'react-dom/client' +import "./index.css"; -createRoot(document.getElementById('root')!).render( - - - , -) +// Import the generated route tree +import { routeTree } from './routeTree.gen' + +// Create a new router instance +const router = createRouter({ routeTree }) + +// Register the router instance for type safety +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} + +// Render the app +const rootElement = document.getElementById('root')! +if (!rootElement.innerHTML) { + const root = ReactDOM.createRoot(rootElement) + root.render( + + + , + ) +} \ No newline at end of file diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts new file mode 100644 index 0000000..d3d440b --- /dev/null +++ b/frontend/src/routeTree.gen.ts @@ -0,0 +1,116 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { createFileRoute } from '@tanstack/react-router' + +// Import Routes + +import { Route as rootRoute } from './routes/__root' + +// Create Virtual Routes + +const LoginLazyImport = createFileRoute('/login')() +const IndexLazyImport = createFileRoute('/')() + +// Create/Update Routes + +const LoginLazyRoute = LoginLazyImport.update({ + id: '/login', + path: '/login', + getParentRoute: () => rootRoute, +} as any).lazy(() => import('./routes/login.lazy').then((d) => d.Route)) + +const IndexLazyRoute = IndexLazyImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRoute, +} as any).lazy(() => import('./routes/index.lazy').then((d) => d.Route)) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexLazyImport + parentRoute: typeof rootRoute + } + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginLazyImport + parentRoute: typeof rootRoute + } + } +} + +// Create and export the route tree + +export interface FileRoutesByFullPath { + '/': typeof IndexLazyRoute + '/login': typeof LoginLazyRoute +} + +export interface FileRoutesByTo { + '/': typeof IndexLazyRoute + '/login': typeof LoginLazyRoute +} + +export interface FileRoutesById { + __root__: typeof rootRoute + '/': typeof IndexLazyRoute + '/login': typeof LoginLazyRoute +} + +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/login' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/login' + id: '__root__' | '/' | '/login' + fileRoutesById: FileRoutesById +} + +export interface RootRouteChildren { + IndexLazyRoute: typeof IndexLazyRoute + LoginLazyRoute: typeof LoginLazyRoute +} + +const rootRouteChildren: RootRouteChildren = { + IndexLazyRoute: IndexLazyRoute, + LoginLazyRoute: LoginLazyRoute, +} + +export const routeTree = rootRoute + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/", + "/login" + ] + }, + "/": { + "filePath": "index.lazy.tsx" + }, + "/login": { + "filePath": "login.lazy.tsx" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx new file mode 100644 index 0000000..b130883 --- /dev/null +++ b/frontend/src/routes/__root.tsx @@ -0,0 +1,11 @@ +import { createRootRoute, Link, Outlet } from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/router-devtools' + +export const Route = createRootRoute({ + component: () => ( + <> + + + + ), +}) \ No newline at end of file diff --git a/frontend/src/routes/index.lazy.tsx b/frontend/src/routes/index.lazy.tsx new file mode 100644 index 0000000..02216f5 --- /dev/null +++ b/frontend/src/routes/index.lazy.tsx @@ -0,0 +1,13 @@ +import { createLazyFileRoute } from '@tanstack/react-router' + +export const Route = createLazyFileRoute('/')({ + component: Index, +}) + +function Index() { + return ( +
+

Welcome Home!

+
+ ) +} \ No newline at end of file diff --git a/frontend/src/routes/login.lazy.tsx b/frontend/src/routes/login.lazy.tsx new file mode 100644 index 0000000..f1ea1e5 --- /dev/null +++ b/frontend/src/routes/login.lazy.tsx @@ -0,0 +1,101 @@ +import { createLazyFileRoute } from '@tanstack/react-router' + +export const Route = createLazyFileRoute('/login')({ + component: Login, +}) + +import { buttonVariants } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { UserAuthForm } from "@/components/auth/UserAuthForm"; + +function Login() { + return ( + <> +
+ Authentication + Authentication +
+
+ + Login + +
+
+
+ + + + Acme Inc +
+
+
+

+ “This library has saved me countless hours of work and + helped me deliver stunning designs to my clients faster than + ever before.” +

+
Sofia Davis
+
+
+
+
+
+
+

+ Create an account +

+

+ Enter your email below to create your account +

+
+ +

+ By clicking continue, you agree to our{" "} + + Terms of Service + {" "} + and{" "} + + Privacy Policy + + . +

+
+
+
+ + ); +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index a625d2c..e1df886 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,10 +1,12 @@ +import { TanStackRouterVite } from '@tanstack/router-plugin/vite' +import react from '@vitejs/plugin-react-swc' import path from 'path' import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react-swc' // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + TanStackRouterVite(), react(), ], resolve: { alias: { "@": path.resolve(__dirname, "./src"), From 6c39716dd63e06532c1fe5358b82a9c8097e9e8a Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 14:59:21 -0600 Subject: [PATCH 082/100] Add proper SVG icon placeholder --- frontend/index.html | 4 +-- frontend/public/linkpulse.svg | 5 ++++ frontend/src/components/icons.tsx | 13 ++++++++-- frontend/src/routes/login.lazy.tsx | 40 ++++++------------------------ 4 files changed, 25 insertions(+), 37 deletions(-) create mode 100644 frontend/public/linkpulse.svg diff --git a/frontend/index.html b/frontend/index.html index e4b78ea..b3297b1 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,9 @@ - + - Vite + React + TS + Linkpulse
diff --git a/frontend/public/linkpulse.svg b/frontend/public/linkpulse.svg new file mode 100644 index 0000000..5444a84 --- /dev/null +++ b/frontend/public/linkpulse.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/icons.tsx b/frontend/src/components/icons.tsx index 97ebf25..c833430 100644 --- a/frontend/src/components/icons.tsx +++ b/frontend/src/components/icons.tsx @@ -1,6 +1,15 @@ -type IconProps = React.HTMLAttributes +type IconProps = React.HTMLAttributes; export const Icons = { + linkpulse: (props: IconProps) => ( + + + + ), logo: (props: IconProps) => ( @@ -145,4 +154,4 @@ export const Icons = { ), -} \ No newline at end of file +}; diff --git a/frontend/src/routes/login.lazy.tsx b/frontend/src/routes/login.lazy.tsx index f1ea1e5..3eaa4d6 100644 --- a/frontend/src/routes/login.lazy.tsx +++ b/frontend/src/routes/login.lazy.tsx @@ -1,38 +1,23 @@ -import { createLazyFileRoute } from '@tanstack/react-router' +import { createLazyFileRoute } from "@tanstack/react-router"; -export const Route = createLazyFileRoute('/login')({ +export const Route = createLazyFileRoute("/login")({ component: Login, -}) +}); import { buttonVariants } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { UserAuthForm } from "@/components/auth/UserAuthForm"; +import { Icons } from "@/components/icons"; function Login() { return ( <> -
- Authentication - Authentication -
-
+
Login @@ -40,18 +25,7 @@ function Login() {
- - - + Acme Inc
From 558c079602010b104e25b7bde00ad68fba3e1c14 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 15:09:29 -0600 Subject: [PATCH 083/100] Reformat frontend with prettier --- frontend/.prettierignore | 1 + frontend/.prettierrc | 4 + frontend/components.json | 2 +- frontend/eslint.config.js | 24 ++--- frontend/postcss.config.js | 2 +- frontend/prettier.config.mjs | 4 +- frontend/src/App.css | 42 -------- frontend/src/App.test.tsx | 4 +- frontend/src/App.tsx | 33 ------ frontend/src/components/auth/UserAuthForm.tsx | 11 +- frontend/src/components/ui/button.tsx | 26 ++--- frontend/src/components/ui/input.tsx | 16 +-- frontend/src/components/ui/label.tsx | 18 ++-- frontend/src/main.tsx | 22 ++-- frontend/src/routes/__root.tsx | 6 +- frontend/src/routes/index.lazy.tsx | 10 +- frontend/src/routes/login.lazy.tsx | 6 +- frontend/tailwind.config.js | 102 +++++++++--------- frontend/vite.config.ts | 13 ++- 19 files changed, 137 insertions(+), 209 deletions(-) create mode 100644 frontend/.prettierrc delete mode 100644 frontend/src/App.css delete mode 100644 frontend/src/App.tsx diff --git a/frontend/.prettierignore b/frontend/.prettierignore index ff7892c..9b2cb4a 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -1,3 +1,4 @@ coverage/ dist/ pnpm-lock.yaml +src/routeTree.gen.ts diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..222861c --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "useTabs": false +} diff --git a/frontend/components.json b/frontend/components.json index 51d59d2..275d416 100644 --- a/frontend/components.json +++ b/frontend/components.json @@ -18,4 +18,4 @@ "hooks": "@/hooks" }, "iconLibrary": "lucide" -} \ No newline at end of file +} diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 092408a..f6acaea 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,28 +1,28 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' +import js from "@eslint/js"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import globals from "globals"; +import tseslint from "typescript-eslint"; export default tseslint.config( - { ignores: ['dist'] }, + { ignores: ["dist"] }, { extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, + "react-hooks": reactHooks, + "react-refresh": reactRefresh, }, rules: { ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', + "react-refresh/only-export-components": [ + "warn", { allowConstantExport: true }, ], }, }, -) +); diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 2e7af2b..2aa7205 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -3,4 +3,4 @@ export default { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/frontend/prettier.config.mjs b/frontend/prettier.config.mjs index 58810b4..16bb77f 100644 --- a/frontend/prettier.config.mjs +++ b/frontend/prettier.config.mjs @@ -1,7 +1,7 @@ export default { plugins: [ - '@ianvs/prettier-plugin-sort-imports', - 'prettier-plugin-tailwindcss', + "@ianvs/prettier-plugin-sort-imports", + "prettier-plugin-tailwindcss", ], singleQuote: true, }; diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index dd62b30..fa9bb2b 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -1,5 +1,5 @@ -import { expect, test } from 'vitest'; +import { expect, test } from "vitest"; -test('App', () => { +test("App", () => { expect(1 + 1).toBe(2); }); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx deleted file mode 100644 index 323b579..0000000 --- a/frontend/src/App.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { useState } from "react"; -import "./App.css"; - -function App() { - const [count, setCount] = useState(0); - - return ( - <> - -

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ); -} - -export default App; diff --git a/frontend/src/components/auth/UserAuthForm.tsx b/frontend/src/components/auth/UserAuthForm.tsx index 7b46c72..5942216 100644 --- a/frontend/src/components/auth/UserAuthForm.tsx +++ b/frontend/src/components/auth/UserAuthForm.tsx @@ -1,12 +1,11 @@ "use client"; -import * as React from "react"; - -import { cn } from "@/lib/utils"; -import { Label } from "@/components/ui/label"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; import { Icons } from "@/components/icons"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { cn } from "@/lib/utils"; +import React from "react"; interface UserAuthFormProps extends React.HTMLAttributes {} diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index 65d4fcd..d09a695 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -1,8 +1,8 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", @@ -31,27 +31,27 @@ const buttonVariants = cva( variant: "default", size: "default", }, - } -) + }, +); export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { - asChild?: boolean + asChild?: boolean; } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" + const Comp = asChild ? Slot : "button"; return ( - ) - } -) -Button.displayName = "Button" + ); + }, +); +Button.displayName = "Button"; -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx index 69b64fb..7db5241 100644 --- a/frontend/src/components/ui/input.tsx +++ b/frontend/src/components/ui/input.tsx @@ -1,6 +1,6 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Input = React.forwardRef>( ({ className, type, ...props }, ref) => { @@ -9,14 +9,14 @@ const Input = React.forwardRef>( type={type} className={cn( "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", - className + className, )} ref={ref} {...props} /> - ) - } -) -Input.displayName = "Input" + ); + }, +); +Input.displayName = "Input"; -export { Input } +export { Input }; diff --git a/frontend/src/components/ui/label.tsx b/frontend/src/components/ui/label.tsx index 683faa7..44912af 100644 --- a/frontend/src/components/ui/label.tsx +++ b/frontend/src/components/ui/label.tsx @@ -1,12 +1,12 @@ -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" -) + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); const Label = React.forwardRef< React.ElementRef, @@ -18,7 +18,7 @@ const Label = React.forwardRef< className={cn(labelVariants(), className)} {...props} /> -)) -Label.displayName = LabelPrimitive.Root.displayName +)); +Label.displayName = LabelPrimitive.Root.displayName; -export { Label } +export { Label }; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index d819966..82a622b 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,28 +1,28 @@ -import { RouterProvider, createRouter } from '@tanstack/react-router' -import { StrictMode } from 'react' -import ReactDOM from 'react-dom/client' +import { RouterProvider, createRouter } from "@tanstack/react-router"; +import { StrictMode } from "react"; +import ReactDOM from "react-dom/client"; import "./index.css"; // Import the generated route tree -import { routeTree } from './routeTree.gen' +import { routeTree } from "./routeTree.gen"; // Create a new router instance -const router = createRouter({ routeTree }) +const router = createRouter({ routeTree }); // Register the router instance for type safety -declare module '@tanstack/react-router' { +declare module "@tanstack/react-router" { interface Register { - router: typeof router + router: typeof router; } } // Render the app -const rootElement = document.getElementById('root')! +const rootElement = document.getElementById("root")!; if (!rootElement.innerHTML) { - const root = ReactDOM.createRoot(rootElement) + const root = ReactDOM.createRoot(rootElement); root.render( , - ) -} \ No newline at end of file + ); +} diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index b130883..c12b2eb 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -1,5 +1,5 @@ -import { createRootRoute, Link, Outlet } from '@tanstack/react-router' -import { TanStackRouterDevtools } from '@tanstack/router-devtools' +import { createRootRoute, Outlet } from "@tanstack/react-router"; +import { TanStackRouterDevtools } from "@tanstack/router-devtools"; export const Route = createRootRoute({ component: () => ( @@ -8,4 +8,4 @@ export const Route = createRootRoute({ ), -}) \ No newline at end of file +}); diff --git a/frontend/src/routes/index.lazy.tsx b/frontend/src/routes/index.lazy.tsx index 02216f5..b1e03d1 100644 --- a/frontend/src/routes/index.lazy.tsx +++ b/frontend/src/routes/index.lazy.tsx @@ -1,13 +1,13 @@ -import { createLazyFileRoute } from '@tanstack/react-router' +import { createLazyFileRoute } from "@tanstack/react-router"; -export const Route = createLazyFileRoute('/')({ +export const Route = createLazyFileRoute("/")({ component: Index, -}) +}); function Index() { return (

Welcome Home!

- ) -} \ No newline at end of file + ); +} diff --git a/frontend/src/routes/login.lazy.tsx b/frontend/src/routes/login.lazy.tsx index 3eaa4d6..1a6772b 100644 --- a/frontend/src/routes/login.lazy.tsx +++ b/frontend/src/routes/login.lazy.tsx @@ -14,19 +14,19 @@ function Login() { <>
- Login + Linkpulse
- Acme Inc + Linkpulse
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index e095f2e..2487604 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,57 +1,57 @@ /** @type {import('tailwindcss').Config} */ export default { - darkMode: ["class"], - content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"], + darkMode: ["class"], + content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"], theme: { - extend: { - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' - }, - colors: { - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))' - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))' - }, - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))' - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))' - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))' - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))' - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))' - }, - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - chart: { - '1': 'hsl(var(--chart-1))', - '2': 'hsl(var(--chart-2))', - '3': 'hsl(var(--chart-3))', - '4': 'hsl(var(--chart-4))', - '5': 'hsl(var(--chart-5))' - } - } - } + extend: { + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + colors: { + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + chart: { + 1: "hsl(var(--chart-1))", + 2: "hsl(var(--chart-2))", + 3: "hsl(var(--chart-3))", + 4: "hsl(var(--chart-4))", + 5: "hsl(var(--chart-5))", + }, + }, + }, }, plugins: [require("tailwindcss-animate")], }; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index e1df886..76b1393 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,15 +1,14 @@ -import { TanStackRouterVite } from '@tanstack/router-plugin/vite' -import react from '@vitejs/plugin-react-swc' -import path from 'path' -import { defineConfig } from 'vite' +import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; +import react from "@vitejs/plugin-react-swc"; +import path from "path"; +import { defineConfig } from "vite"; // https://vite.dev/config/ export default defineConfig({ - plugins: [ - TanStackRouterVite(), react(), ], + plugins: [TanStackRouterVite(), react()], resolve: { alias: { "@": path.resolve(__dirname, "./src"), }, }, -}) +}); From fb0193bc76518e5f99472b843698fb0425b30e6b Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 16:06:50 -0600 Subject: [PATCH 084/100] Disable quotes, fix padding & rendering, update CHANGELOG.md --- CHANGELOG.md | 2 +- frontend/src/routes/login.lazy.tsx | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9a878c..8d6df85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - backend: Simple `/health` & `/api/migrations` endpoint tests - backend: `utc_now` helper function - backend: `pwdlib[argon2]`, `pytest` (`pytest-cov`, `pytest-xdist`), `limits`, `httpx`, `email-validator` pacakges -- frontend: Added `clsx` package +- frontend: Re-initialized with `vite` template, setup `@tanstack/router` & `shadcn` components. ## Changed diff --git a/frontend/src/routes/login.lazy.tsx b/frontend/src/routes/login.lazy.tsx index 1a6772b..ef46739 100644 --- a/frontend/src/routes/login.lazy.tsx +++ b/frontend/src/routes/login.lazy.tsx @@ -12,7 +12,7 @@ import { Icons } from "@/components/icons"; function Login() { return ( <> -
+
Linkpulse -
+
Linkpulse
-
-
+
+ {/*

“This library has saved me countless hours of work and helped me deliver stunning designs to my clients faster than ever before.”

Sofia Davis
-
+
*/} + {/*

+
*/}
-
-
+
+

Create an account @@ -50,7 +52,7 @@ function Login() {

-

+

By clicking continue, you agree to our{" "} Date: Sun, 10 Nov 2024 19:02:30 -0600 Subject: [PATCH 085/100] Remove outdated CHANGELOG entries, simplify imports, remove Next.js specifier --- CHANGELOG.md | 3 +-- frontend/src/components/auth/UserAuthForm.tsx | 8 +++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d6df85..cd6f25f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,11 +27,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Set `black` formatter line length to 120 characters - backend: migration squashing threshold to 15 - backend: moved top level `app` routes to `router.misc` -- frontend: Updated `eslint` to `9.x.x`, `@types/node` to `22.9.x` ## Removed -- frontend: Removed `@nkzw/eslint-config` package +- frontend: Most old packages from initial `vite` template - backend: `IPAddress` Model (definition + DB state via migration) & all related code ## [0.2.2] - 2024-11-01 diff --git a/frontend/src/components/auth/UserAuthForm.tsx b/frontend/src/components/auth/UserAuthForm.tsx index 5942216..e2969bf 100644 --- a/frontend/src/components/auth/UserAuthForm.tsx +++ b/frontend/src/components/auth/UserAuthForm.tsx @@ -1,16 +1,14 @@ -"use client"; - import { Icons } from "@/components/icons"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { cn } from "@/lib/utils"; -import React from "react"; +import { HTMLAttributes, useState } from "react"; -interface UserAuthFormProps extends React.HTMLAttributes {} +interface UserAuthFormProps extends HTMLAttributes {} export function UserAuthForm({ className, ...props }: UserAuthFormProps) { - const [isLoading, setIsLoading] = React.useState(false); + const [isLoading, setIsLoading] = useState(false); async function onSubmit(event: React.SyntheticEvent) { event.preventDefault(); From 8ad52a9e839de60f871191baa129617cb8c31193 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 19:03:37 -0600 Subject: [PATCH 086/100] Update cSpell words/paths --- .vscode/settings.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 81ebc80..f9fa73f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,13 @@ { "cSpell.words": [ "apscheduler", + "Avenir", "backref", "bpython", "Callsite", "clsx", "excepthook", + "httpx", "humanfs", "humanwhocodes", "inmemory", @@ -14,7 +16,9 @@ "migratehistory", "Nixpacks", "nkzw", + "nocheck", "ORJSON", + "pacakges", "pext", "postcss", "pwdlib", @@ -24,12 +28,16 @@ "riscv", "rollup", "rtype", + "shadcn", "starlette", "structlog", "sugarss", "tailwindcss", + "tanstack", "timestamper", - "vitest" + "tseslint", + "vitest", + "xdist" ], "python.analysis.extraPaths": ["./backend/"], "editor.formatOnSave": true, @@ -47,6 +55,7 @@ "settings.json", "*.lock", "*lock.*", - "package.json" + "package.json", + "routeTree.gen.ts" ] } From fa8434b41840ac7cb0a777f3275c0e50370f008e Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 19:37:05 -0600 Subject: [PATCH 087/100] Enable zstd encoding, use gzip level 3 --- frontend/Caddyfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/Caddyfile b/frontend/Caddyfile index 0f8a2d5..cf67267 100644 --- a/frontend/Caddyfile +++ b/frontend/Caddyfile @@ -16,7 +16,10 @@ # site block, listens on the $PORT environment variable, automatically assigned by railway :{$PORT} { respond /health 200 - encode gzip + encode { + zstd fastest + gzip 3 + } log { # access logs From c463546e8f43eb012f9044ccc06c3b568355bf5e Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 19:47:57 -0600 Subject: [PATCH 088/100] Fix delete_cookie() usage, basic /api/session route, remove unused SessionModel dataclass --- backend/linkpulse/dependencies.py | 7 ------- backend/linkpulse/routers/auth.py | 14 +++++++++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/backend/linkpulse/dependencies.py b/backend/linkpulse/dependencies.py index 8738efc..7aaee02 100644 --- a/backend/linkpulse/dependencies.py +++ b/backend/linkpulse/dependencies.py @@ -44,13 +44,6 @@ class RateLimiter: return True -@dataclass -class SessionModel: - user_id: str - session_id: str - expires_at: int - - class SessionDependency: def __init__(self, required: bool = False): self.required = required diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index 3f0aca6..aca1dc0 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -3,7 +3,7 @@ from typing import Annotated, Optional, Tuple import structlog from fastapi import APIRouter, Depends, Response, status -from linkpulse.dependencies import RateLimiter, SessionDependency, SessionModel +from linkpulse.dependencies import RateLimiter, SessionDependency from linkpulse.models import Session, User from linkpulse.utilities import utc_now from pwdlib import PasswordHash @@ -126,7 +126,7 @@ async def logout( count = Session.delete().where(Session.user == session.user).execute() logger.debug("All sessions deleted", user=session.user.email, count=count, source_token=session.token) - response.delete_cookie("session", "", max_age=0) + response.delete_cookie("session") @router.post("/api/register") @@ -141,13 +141,17 @@ async def register(): @router.get("/api/session") -async def session(session: Annotated[SessionModel, Depends(SessionDependency(required=True))]): +async def session(session: Annotated[Session, Depends(SessionDependency(required=True))]): # Returns the session information for the current session - return {} + return { + "user": { + "email": session.user.email, + } + } @router.get("/api/sessions") -async def sessions(session: Annotated[SessionModel, Depends(SessionDependency(required=True))]): +async def sessions(session: Annotated[Session, Depends(SessionDependency(required=True))]): # Returns a list of all active sessions for this user return {} From df36b816cc78f890c339c8ec6757bdce7c453cf9 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 19:50:55 -0600 Subject: [PATCH 089/100] Move python-based .gitignore at root into /backend interfering with frontend --- .gitignore => backend/.gitignore | 1 - 1 file changed, 1 deletion(-) rename .gitignore => backend/.gitignore (99%) diff --git a/.gitignore b/backend/.gitignore similarity index 99% rename from .gitignore rename to backend/.gitignore index 00ccd3e..74d08e5 100644 --- a/.gitignore +++ b/backend/.gitignore @@ -1,4 +1,3 @@ -.env pytest.xml pytest-coverage.txt From 00620539d85569e574b353552965d223d81156d9 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 19:51:44 -0600 Subject: [PATCH 090/100] Re-add root .gitignore, add missing src/lib/utils.ts shadcn helper --- .gitignore | 1 + frontend/src/lib/utils.ts | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 .gitignore create mode 100644 frontend/src/lib/utils.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts new file mode 100644 index 0000000..a5ef193 --- /dev/null +++ b/frontend/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} From af5e42abe7efc863b59363a966714bb1dc7cd123 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 20:40:58 -0600 Subject: [PATCH 091/100] Log detail CORSMiddleware setup --- backend/linkpulse/app.py | 11 +++++++---- backend/linkpulse/routers/auth.py | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/linkpulse/app.py b/backend/linkpulse/app.py index 1713d0a..8ebf18a 100644 --- a/backend/linkpulse/app.py +++ b/backend/linkpulse/app.py @@ -55,16 +55,19 @@ logger = structlog.get_logger() if is_development: from fastapi.middleware.cors import CORSMiddleware + origins = [ + "http://localhost", + "http://localhost:5173", + ] + app.add_middleware( CORSMiddleware, - allow_origins=[ - "http://localhost", - "http://localhost:5173", - ], + allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) + logger.info("CORS Enabled", origins=origins) app.add_middleware(LoggingMiddleware) app.add_middleware(CorrelationIdMiddleware) diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index aca1dc0..191bc50 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -15,6 +15,7 @@ logger = structlog.get_logger() router = APIRouter() hasher = PasswordHash([Argon2Hasher()]) +# cspell: disable dummy_hash = ( "$argon2id$v=19$m=65536,t=3,p=4$Ii3hm5/NqcJddQDFK24Wtw$I99xV/qkaLROo0VZcvaZrYMAD9RTcWzxY5/RbMoRLQ4" ) From f18adf0f4186f6eb2fc7e00a856a6fec19f3b241 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 20:41:58 -0600 Subject: [PATCH 092/100] Add zustand, true-myth, primitive authentication state, startup getSession() --- frontend/package.json | 4 +- frontend/pnpm-lock.yaml | 36 ++++++++++++++ frontend/src/lib/auth.ts | 37 ++++++++++++++ frontend/src/lib/state.ts | 19 +++++++ frontend/src/main.tsx | 3 ++ frontend/src/routeTree.gen.ts | 49 ++++++++++++++----- frontend/src/routes/dashboard.tsx | 9 ++++ .../src/routes/{login.lazy.tsx => login.tsx} | 21 ++++++-- 8 files changed, 159 insertions(+), 19 deletions(-) create mode 100644 frontend/src/lib/auth.ts create mode 100644 frontend/src/lib/state.ts create mode 100644 frontend/src/routes/dashboard.tsx rename frontend/src/routes/{login.lazy.tsx => login.tsx} (86%) diff --git a/frontend/package.json b/frontend/package.json index 5ddd6e5..9906d00 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,7 +24,9 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "tailwind-merge": "^2.5.4", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "true-myth": "^8.0.1", + "zustand": "^5.0.1" }, "devDependencies": { "@eslint/js": "^9.13.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index b06b89e..e8ce723 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -38,6 +38,12 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.14) + true-myth: + specifier: ^8.0.1 + version: 8.0.1 + zustand: + specifier: ^5.0.1 + version: 5.0.1(@types/react@18.3.12)(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1)) devDependencies: '@eslint/js': specifier: ^9.13.0 @@ -1746,6 +1752,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + true-myth@8.0.1: + resolution: {integrity: sha512-/6Tga43I+LfMJmg7hiCGI8KqjjlDEa1XJnWTZOKmmyQ9s7Kv4WPmpoQT/LST0Q98GAnsB+UPtGFwVHi8h7+qFw==} + engines: {node: 18.* || >= 20.*} + ts-api-utils@1.4.0: resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} engines: {node: '>=16'} @@ -1908,6 +1918,24 @@ packages: zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + zustand@5.0.1: + resolution: {integrity: sha512-pRET7Lao2z+n5R/HduXMio35TncTlSW68WsYBq2Lg1ASspsNGjpwLAsij3RpouyV6+kHMwwwzP0bZPD70/Jx/w==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -3433,6 +3461,8 @@ snapshots: dependencies: is-number: 7.0.0 + true-myth@8.0.1: {} + ts-api-utils@1.4.0(typescript@5.6.3): dependencies: typescript: 5.6.3 @@ -3579,3 +3609,9 @@ snapshots: yocto-queue@0.1.0: {} zod@3.23.8: {} + + zustand@5.0.1(@types/react@18.3.12)(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1)): + optionalDependencies: + '@types/react': 18.3.12 + react: 18.3.1 + use-sync-external-store: 1.2.2(react@18.3.1) diff --git a/frontend/src/lib/auth.ts b/frontend/src/lib/auth.ts new file mode 100644 index 0000000..cd894d3 --- /dev/null +++ b/frontend/src/lib/auth.ts @@ -0,0 +1,37 @@ +import Result, { ok, err } from "true-myth/result"; +import { useUserStore } from "./state"; + +// Authentication on the frontend is managed by Zustand's state. +// Upon application load, a single request is made to acquire session state. +// Any route that requires authentication will redirect if a valid session is not acquired. +// The session state is stored in the user store. +// If any protected API call suddenly fails with a 401 status code, the user store is reset, a logout message is displayed, and the user is redirected to the login page. +// All redirects to the login page will carry a masked URL parameter that can be used to redirect the user back to the page they were on after logging in. + +const TARGET = import.meta.env.DEV + ? `http://${import.meta.env.VITE_BACKEND_TARGET}` + : ""; + +console.log({ env: import.meta.env, target: TARGET }); +type ErrorResponse = { + detail: string; +}; + +type SessionResponse = { + user: {}; +}; + +export const getSession = async (): Promise< + Result +> => { + const response = await fetch(TARGET + "/api/session"); + if (response.ok) { + const user = await response.json(); + useUserStore.getState().setUser(user); + return ok({ user }); + } else { + useUserStore.getState().logout(); + const error = await response.json(); + return err({ detail: error.detail }); + } +}; diff --git a/frontend/src/lib/state.ts b/frontend/src/lib/state.ts new file mode 100644 index 0000000..728fcb1 --- /dev/null +++ b/frontend/src/lib/state.ts @@ -0,0 +1,19 @@ +import { create } from "zustand"; + +type UserState = { + user: { + // TODO: This will eventually carry more user information (name, avatar, etc.) + email: string; + } | null; +}; + +type UserActions = { + setUser: (user: UserState["user"]) => void; + logout: () => void; +}; + +export const useUserStore = create((set) => ({ + user: null, + setUser: (user) => set({ user }), + logout: () => set({ user: null }), +})); diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 82a622b..12f00c3 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,5 +1,6 @@ import { RouterProvider, createRouter } from "@tanstack/react-router"; import { StrictMode } from "react"; +import { getSession } from "@/lib/auth"; import ReactDOM from "react-dom/client"; import "./index.css"; @@ -9,6 +10,8 @@ import { routeTree } from "./routeTree.gen"; // Create a new router instance const router = createRouter({ routeTree }); +getSession(); + // Register the router instance for type safety declare module "@tanstack/react-router" { interface Register { diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index d3d440b..d5d7791 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -13,19 +13,26 @@ import { createFileRoute } from '@tanstack/react-router' // Import Routes import { Route as rootRoute } from './routes/__root' +import { Route as LoginImport } from './routes/login' +import { Route as DashboardImport } from './routes/dashboard' // Create Virtual Routes -const LoginLazyImport = createFileRoute('/login')() const IndexLazyImport = createFileRoute('/')() // Create/Update Routes -const LoginLazyRoute = LoginLazyImport.update({ +const LoginRoute = LoginImport.update({ id: '/login', path: '/login', getParentRoute: () => rootRoute, -} as any).lazy(() => import('./routes/login.lazy').then((d) => d.Route)) +} as any) + +const DashboardRoute = DashboardImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => rootRoute, +} as any) const IndexLazyRoute = IndexLazyImport.update({ id: '/', @@ -44,11 +51,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexLazyImport parentRoute: typeof rootRoute } + '/dashboard': { + id: '/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof DashboardImport + parentRoute: typeof rootRoute + } '/login': { id: '/login' path: '/login' fullPath: '/login' - preLoaderRoute: typeof LoginLazyImport + preLoaderRoute: typeof LoginImport parentRoute: typeof rootRoute } } @@ -58,37 +72,42 @@ declare module '@tanstack/react-router' { export interface FileRoutesByFullPath { '/': typeof IndexLazyRoute - '/login': typeof LoginLazyRoute + '/dashboard': typeof DashboardRoute + '/login': typeof LoginRoute } export interface FileRoutesByTo { '/': typeof IndexLazyRoute - '/login': typeof LoginLazyRoute + '/dashboard': typeof DashboardRoute + '/login': typeof LoginRoute } export interface FileRoutesById { __root__: typeof rootRoute '/': typeof IndexLazyRoute - '/login': typeof LoginLazyRoute + '/dashboard': typeof DashboardRoute + '/login': typeof LoginRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/login' + fullPaths: '/' | '/dashboard' | '/login' fileRoutesByTo: FileRoutesByTo - to: '/' | '/login' - id: '__root__' | '/' | '/login' + to: '/' | '/dashboard' | '/login' + id: '__root__' | '/' | '/dashboard' | '/login' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexLazyRoute: typeof IndexLazyRoute - LoginLazyRoute: typeof LoginLazyRoute + DashboardRoute: typeof DashboardRoute + LoginRoute: typeof LoginRoute } const rootRouteChildren: RootRouteChildren = { IndexLazyRoute: IndexLazyRoute, - LoginLazyRoute: LoginLazyRoute, + DashboardRoute: DashboardRoute, + LoginRoute: LoginRoute, } export const routeTree = rootRoute @@ -102,14 +121,18 @@ export const routeTree = rootRoute "filePath": "__root.tsx", "children": [ "/", + "/dashboard", "/login" ] }, "/": { "filePath": "index.lazy.tsx" }, + "/dashboard": { + "filePath": "dashboard.tsx" + }, "/login": { - "filePath": "login.lazy.tsx" + "filePath": "login.tsx" } } } diff --git a/frontend/src/routes/dashboard.tsx b/frontend/src/routes/dashboard.tsx new file mode 100644 index 0000000..1b14c8c --- /dev/null +++ b/frontend/src/routes/dashboard.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/dashboard")({ + component: RouteComponent, +}); + +function RouteComponent() { + return "Hello /dashboard!"; +} diff --git a/frontend/src/routes/login.lazy.tsx b/frontend/src/routes/login.tsx similarity index 86% rename from frontend/src/routes/login.lazy.tsx rename to frontend/src/routes/login.tsx index ef46739..146803e 100644 --- a/frontend/src/routes/login.lazy.tsx +++ b/frontend/src/routes/login.tsx @@ -1,14 +1,25 @@ -import { createLazyFileRoute } from "@tanstack/react-router"; - -export const Route = createLazyFileRoute("/login")({ - component: Login, -}); +import { createFileRoute, redirect } from "@tanstack/react-router"; +import { useUserStore } from "@/lib/state"; import { buttonVariants } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { UserAuthForm } from "@/components/auth/UserAuthForm"; import { Icons } from "@/components/icons"; +export const Route = createFileRoute("/login")({ + beforeLoad: async ({ location }) => { + const isLoggedIn = useUserStore.getState().user !== null; + + if (isLoggedIn) { + return redirect({ + to: "/dashboard", + search: { redirect: location.href }, + }); + } + }, + component: Login, +}); + function Login() { return ( <> From d9d2e04d9485434ab88c1f78cd8132265417abb9 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 22:51:54 -0600 Subject: [PATCH 093/100] Add /register route, split up authentication page --- .vscode/settings.json | 3 +- frontend/src/components/auth/UserAuthForm.tsx | 68 ----------- frontend/src/components/auth/form.tsx | 108 ++++++++++++++++++ .../src/components/pages/authentication.tsx | 67 +++++++++++ frontend/src/routeTree.gen.ts | 31 ++++- frontend/src/routes/login.tsx | 77 ++----------- frontend/src/routes/register.tsx | 40 +++++++ 7 files changed, 256 insertions(+), 138 deletions(-) delete mode 100644 frontend/src/components/auth/UserAuthForm.tsx create mode 100644 frontend/src/components/auth/form.tsx create mode 100644 frontend/src/components/pages/authentication.tsx create mode 100644 frontend/src/routes/register.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index f9fa73f..a880bab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -37,7 +37,8 @@ "timestamper", "tseslint", "vitest", - "xdist" + "xdist", + "Zustand's" ], "python.analysis.extraPaths": ["./backend/"], "editor.formatOnSave": true, diff --git a/frontend/src/components/auth/UserAuthForm.tsx b/frontend/src/components/auth/UserAuthForm.tsx deleted file mode 100644 index e2969bf..0000000 --- a/frontend/src/components/auth/UserAuthForm.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Icons } from "@/components/icons"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { cn } from "@/lib/utils"; -import { HTMLAttributes, useState } from "react"; - -interface UserAuthFormProps extends HTMLAttributes {} - -export function UserAuthForm({ className, ...props }: UserAuthFormProps) { - const [isLoading, setIsLoading] = useState(false); - - async function onSubmit(event: React.SyntheticEvent) { - event.preventDefault(); - setIsLoading(true); - - setTimeout(() => { - setIsLoading(false); - }, 3000); - } - - return ( -

- ); -} diff --git a/frontend/src/components/auth/form.tsx b/frontend/src/components/auth/form.tsx new file mode 100644 index 0000000..fb09104 --- /dev/null +++ b/frontend/src/components/auth/form.tsx @@ -0,0 +1,108 @@ +import { Icons } from "@/components/icons"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { cn } from "@/lib/utils"; +import { Link } from "@tanstack/react-router"; +import { HTMLAttributes, useState } from "react"; + +interface UserAuthFormProps extends HTMLAttributes {} + +export function RegisterForm({ className, ...props }: UserAuthFormProps) { + const [isLoading, setIsLoading] = useState(false); + + async function onSubmit(event: React.SyntheticEvent) { + event.preventDefault(); + setIsLoading(true); + + setTimeout(() => { + setIsLoading(false); + }, 3000); + } + + return ( +
+
+
+
+ + +
+ +
+
+
+ ); +} + +export function LoginForm({ className, ...props }: UserAuthFormProps) { + const [isLoading, setIsLoading] = useState(false); + + async function onSubmit(event: React.SyntheticEvent) { + event.preventDefault(); + setIsLoading(true); + + setTimeout(() => { + setIsLoading(false); + }, 3000); + } + + return ( +
+
+
+
+ + +
+ +
+
+

+ + Register + {" "} + or{" "} + + Forgot Password + +

+
+ ); +} diff --git a/frontend/src/components/pages/authentication.tsx b/frontend/src/components/pages/authentication.tsx new file mode 100644 index 0000000..dea7caf --- /dev/null +++ b/frontend/src/components/pages/authentication.tsx @@ -0,0 +1,67 @@ +import { Icons } from "@/components/icons"; +import { buttonVariants } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { Link } from "@tanstack/react-router"; +import { ReactNode } from "react"; + +export function AgreementText() { + return ( +

+ By clicking continue, you agree to our{" "} + + Terms of Service + {" "} + and{" "} + + Privacy Policy + + . +

+ ); +} + +export default function AuthenticationPage({ + children, +}: { + children: ReactNode; +}) { + return ( +
+ + Linkpulse + +
+
+
+ + Linkpulse +
+
+ {/*
+

+ “This library has saved me countless hours of work and + helped me deliver stunning designs to my clients faster than + ever before.” +

+
Sofia Davis
+
*/} + {/*

+
*/} +
+
+
{children}
+
+ ); +} diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index d5d7791..36868a8 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -13,6 +13,7 @@ import { createFileRoute } from '@tanstack/react-router' // Import Routes import { Route as rootRoute } from './routes/__root' +import { Route as RegisterImport } from './routes/register' import { Route as LoginImport } from './routes/login' import { Route as DashboardImport } from './routes/dashboard' @@ -22,6 +23,12 @@ const IndexLazyImport = createFileRoute('/')() // Create/Update Routes +const RegisterRoute = RegisterImport.update({ + id: '/register', + path: '/register', + getParentRoute: () => rootRoute, +} as any) + const LoginRoute = LoginImport.update({ id: '/login', path: '/login', @@ -65,6 +72,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginImport parentRoute: typeof rootRoute } + '/register': { + id: '/register' + path: '/register' + fullPath: '/register' + preLoaderRoute: typeof RegisterImport + parentRoute: typeof rootRoute + } } } @@ -74,12 +88,14 @@ export interface FileRoutesByFullPath { '/': typeof IndexLazyRoute '/dashboard': typeof DashboardRoute '/login': typeof LoginRoute + '/register': typeof RegisterRoute } export interface FileRoutesByTo { '/': typeof IndexLazyRoute '/dashboard': typeof DashboardRoute '/login': typeof LoginRoute + '/register': typeof RegisterRoute } export interface FileRoutesById { @@ -87,14 +103,15 @@ export interface FileRoutesById { '/': typeof IndexLazyRoute '/dashboard': typeof DashboardRoute '/login': typeof LoginRoute + '/register': typeof RegisterRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/dashboard' | '/login' + fullPaths: '/' | '/dashboard' | '/login' | '/register' fileRoutesByTo: FileRoutesByTo - to: '/' | '/dashboard' | '/login' - id: '__root__' | '/' | '/dashboard' | '/login' + to: '/' | '/dashboard' | '/login' | '/register' + id: '__root__' | '/' | '/dashboard' | '/login' | '/register' fileRoutesById: FileRoutesById } @@ -102,12 +119,14 @@ export interface RootRouteChildren { IndexLazyRoute: typeof IndexLazyRoute DashboardRoute: typeof DashboardRoute LoginRoute: typeof LoginRoute + RegisterRoute: typeof RegisterRoute } const rootRouteChildren: RootRouteChildren = { IndexLazyRoute: IndexLazyRoute, DashboardRoute: DashboardRoute, LoginRoute: LoginRoute, + RegisterRoute: RegisterRoute, } export const routeTree = rootRoute @@ -122,7 +141,8 @@ export const routeTree = rootRoute "children": [ "/", "/dashboard", - "/login" + "/login", + "/register" ] }, "/": { @@ -133,6 +153,9 @@ export const routeTree = rootRoute }, "/login": { "filePath": "login.tsx" + }, + "/register": { + "filePath": "register.tsx" } } } diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index 146803e..96eb277 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -1,10 +1,8 @@ -import { createFileRoute, redirect } from "@tanstack/react-router"; import { useUserStore } from "@/lib/state"; +import { createFileRoute, redirect } from "@tanstack/react-router"; -import { buttonVariants } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; -import { UserAuthForm } from "@/components/auth/UserAuthForm"; -import { Icons } from "@/components/icons"; +import { LoginForm } from "@/components/auth/form"; +import AuthenticationPage from "@/components/pages/authentication"; export const Route = createFileRoute("/login")({ beforeLoad: async ({ location }) => { @@ -22,67 +20,16 @@ export const Route = createFileRoute("/login")({ function Login() { return ( - <> -
- - Linkpulse - -
-
-
- - Linkpulse -
-
- {/*
-

- “This library has saved me countless hours of work and - helped me deliver stunning designs to my clients faster than - ever before.” -

-
Sofia Davis
-
*/} - {/*

-
*/} -
-
-
-
-
-

- Create an account -

-

- Enter your email below to create your account -

-
- -

- By clicking continue, you agree to our{" "} - - Terms of Service - {" "} - and{" "} - - Privacy Policy - - . -

-
+ +
+
+

Login

+

+ Enter your email below to login +

+
- +
); } diff --git a/frontend/src/routes/register.tsx b/frontend/src/routes/register.tsx new file mode 100644 index 0000000..60e90dd --- /dev/null +++ b/frontend/src/routes/register.tsx @@ -0,0 +1,40 @@ +import { useUserStore } from "@/lib/state"; +import { createFileRoute, redirect } from "@tanstack/react-router"; + +import { RegisterForm } from "@/components/auth/form"; +import AuthenticationPage, { + AgreementText, +} from "@/components/pages/authentication"; + +export const Route = createFileRoute("/register")({ + beforeLoad: async ({ location }) => { + const isLoggedIn = useUserStore.getState().user !== null; + + if (isLoggedIn) { + return redirect({ + to: "/dashboard", + search: { redirect: location.href }, + }); + } + }, + component: Register, +}); + +function Register() { + return ( + +
+
+

+ Create an account +

+

+ Enter your email below to create your account +

+
+ + +
+
+ ); +} From 9990bcab025c85e61af8eff547e46364429bdcd0 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 23:07:15 -0600 Subject: [PATCH 094/100] Add /api/login usage --- frontend/src/components/auth/form.tsx | 47 +++++++++++++++++---------- frontend/src/lib/auth.ts | 27 +++++++++++++++ frontend/src/routes/login.tsx | 4 +-- 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/auth/form.tsx b/frontend/src/components/auth/form.tsx index fb09104..95dc7cc 100644 --- a/frontend/src/components/auth/form.tsx +++ b/frontend/src/components/auth/form.tsx @@ -2,22 +2,22 @@ import { Icons } from "@/components/icons"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { login } from "@/lib/auth"; import { cn } from "@/lib/utils"; import { Link } from "@tanstack/react-router"; -import { HTMLAttributes, useState } from "react"; +import { HTMLAttributes, SyntheticEvent, useState } from "react"; interface UserAuthFormProps extends HTMLAttributes {} export function RegisterForm({ className, ...props }: UserAuthFormProps) { const [isLoading, setIsLoading] = useState(false); - async function onSubmit(event: React.SyntheticEvent) { + async function onSubmit(event: SyntheticEvent) { event.preventDefault(); - setIsLoading(true); - setTimeout(() => { - setIsLoading(false); - }, 3000); + setIsLoading(true); + // await login() + setIsLoading(false); } return ( @@ -42,7 +42,7 @@ export function RegisterForm({ className, ...props }: UserAuthFormProps) { {isLoading && ( )} - Sign In with Email + Create Account
@@ -53,13 +53,16 @@ export function RegisterForm({ className, ...props }: UserAuthFormProps) { export function LoginForm({ className, ...props }: UserAuthFormProps) { const [isLoading, setIsLoading] = useState(false); - async function onSubmit(event: React.SyntheticEvent) { + async function onSubmit(event: SyntheticEvent) { event.preventDefault(); - setIsLoading(true); - setTimeout(() => { - setIsLoading(false); - }, 3000); + const email = (event.target as HTMLFormElement).email.value; + const password = (event.target as HTMLFormElement).password.value; + + setIsLoading(true); + const result = await login(email, password); + console.log({ result }); + setIsLoading(false); } return ( @@ -67,12 +70,9 @@ export function LoginForm({ className, ...props }: UserAuthFormProps) {
- +
+
+ + +

Register diff --git a/frontend/src/lib/auth.ts b/frontend/src/lib/auth.ts index cd894d3..d3a5c1a 100644 --- a/frontend/src/lib/auth.ts +++ b/frontend/src/lib/auth.ts @@ -35,3 +35,30 @@ export const getSession = async (): Promise< return err({ detail: error.detail }); } }; + +type LoginResponse = { + email: string; + expiry: string; +}; + +export const login = async ( + email: string, + password: string, +): Promise> => { + const response = await fetch(TARGET + "/api/login", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, password }), + }); + + if (response.ok) { + const user = await response.json(); + useUserStore.getState().setUser(user); + return ok(user); + } else { + const error = await response.json(); + return err({ detail: error.detail }); + } +}; diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index 96eb277..db38e98 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -23,9 +23,9 @@ function Login() {

-

Login

+

Sign In

- Enter your email below to login + Enter your email & password below to login

From f14285b252e32fa543333490f7490bdf719bda26 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 10 Nov 2024 23:40:00 -0600 Subject: [PATCH 095/100] Fixup hacky login session system, add separate development Caddyfile for CORS cookie issue --- backend/linkpulse/routers/auth.py | 5 +++-- frontend/Caddyfile.development | 18 ++++++++++++++++++ frontend/src/components/auth/form.tsx | 10 ++++++++-- frontend/src/lib/auth.ts | 23 ++++++++++++++++++++--- frontend/src/lib/state.ts | 4 +++- frontend/src/main.tsx | 2 -- frontend/src/routes/dashboard.tsx | 23 +++++++++++++++++++++-- frontend/src/routes/login.tsx | 6 +++--- 8 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 frontend/Caddyfile.development diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index 191bc50..f090104 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -5,7 +5,7 @@ import structlog from fastapi import APIRouter, Depends, Response, status from linkpulse.dependencies import RateLimiter, SessionDependency from linkpulse.models import Session, User -from linkpulse.utilities import utc_now +from linkpulse.utilities import utc_now, is_development from pwdlib import PasswordHash from pwdlib.hashers.argon2 import Argon2Hasher from pydantic import BaseModel, EmailStr, Field @@ -109,7 +109,8 @@ async def login(body: LoginBody, response: Response): ) # Set Cookie of session token - response.set_cookie("session", token, samesite="strict", max_age=int(session_duration.total_seconds())) + max_age = int(session_duration.total_seconds()) + response.set_cookie("session", token, max_age=max_age, secure=not is_development, httponly=True) return {"email": user.email, "expiry": session.expiry} diff --git a/frontend/Caddyfile.development b/frontend/Caddyfile.development new file mode 100644 index 0000000..82282c7 --- /dev/null +++ b/frontend/Caddyfile.development @@ -0,0 +1,18 @@ +{ + admin off # theres no need for the admin api in railway's environment + auto_https off # railway handles https for us, this would cause issues if left enabled +} + +http://localhost:8080 { + respond /health 200 + encode { + zstd fastest + gzip 3 + } + + handle /api/* { + reverse_proxy localhost:8000 + } + + reverse_proxy localhost:5173 +} diff --git a/frontend/src/components/auth/form.tsx b/frontend/src/components/auth/form.tsx index 95dc7cc..fbbca23 100644 --- a/frontend/src/components/auth/form.tsx +++ b/frontend/src/components/auth/form.tsx @@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { login } from "@/lib/auth"; +import { useUserStore } from "@/lib/state"; import { cn } from "@/lib/utils"; import { Link } from "@tanstack/react-router"; import { HTMLAttributes, SyntheticEvent, useState } from "react"; @@ -38,7 +39,7 @@ export function RegisterForm({ className, ...props }: UserAuthFormProps) { disabled={isLoading} />
-