From 166a4926057d7de8e02ebd01b38b8f10667188ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?= Date: Thu, 1 Oct 2026 00:08:25 +0700 Subject: [PATCH 1/8] build: upgrade Kotlin to 2.3.21, Compose Multiplatform to 1.10.3, Decompose to 3.5.0 Regenerated API dumps (only additive synthetic $default entries from the new compiler). Removed the stale JS kotlin-js-store/yarn.lock; the active wasm lockfile no longer resolves the vulnerable qs / serialize-javascript / webpack versions. --- gradle/libs.versions.toml | 10 +- kotlin-js-store/yarn.lock | 2065 --------------------------------- krelay/api/android/krelay.api | 14 + krelay/api/jvm/krelay.api | 14 + 4 files changed, 33 insertions(+), 2070 deletions(-) delete mode 100644 kotlin-js-store/yarn.lock diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 168b0a5..2e773c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,11 +10,11 @@ androidx-core = "1.17.0" androidx-espresso = "3.7.0" androidx-lifecycle = "2.9.6" androidx-testExt = "1.3.0" -composeMultiplatform = "1.8.0-alpha03" +composeMultiplatform = "1.10.3" junit = "4.13.2" -kotlin = "2.1.0" +kotlin = "2.3.21" bcv = "0.17.0" -material3 = "1.8.0-alpha03" +material3 = "1.9.0" # Integration libraries moko-permissions = "0.18.0" @@ -22,8 +22,8 @@ moko-biometry = "0.4.0" peekaboo = "0.5.2" playcore-review = "2.0.1" biometric = "1.1.0" -decompose = "3.4.0-alpha03" -decompose-extensions = "3.4.0-alpha03" +decompose = "3.5.0" +decompose-extensions = "3.5.0" voyager = "1.1.0-beta03" [libraries] diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock deleted file mode 100644 index 550d166..0000000 --- a/kotlin-js-store/yarn.lock +++ /dev/null @@ -1,2065 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@discoveryjs/json-ext@^0.5.0": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" - integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" - integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/source-map@^0.3.3": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.11.tgz#b21835cbd36db656b857c2ad02ebd413cc13a9ba" - integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - -"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz#f4c663e862f06dc98ca4d453862c46902789a18d" - integrity sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.31" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" - integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@socket.io/component-emitter@~3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" - integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== - -"@types/cors@^2.8.12": - version "2.8.19" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" - integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== - dependencies: - "@types/node" "*" - -"@types/estree@^1.0.5": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24" - integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== - -"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/node@*", "@types/node@>=10.0.0": - version "26.4.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-26.4.1.tgz#3d8dc80515894958448ee266cf5d6bc3e5205bd5" - integrity sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA== - dependencies: - undici-types "~8.3.0" - -"@types/ws@^8.5.12": - version "8.18.1" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" - integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== - dependencies: - "@types/node" "*" - -"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.12.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" - integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== - dependencies: - "@webassemblyjs/helper-numbers" "1.13.2" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - -"@webassemblyjs/floating-point-hex-parser@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" - integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== - -"@webassemblyjs/helper-api-error@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" - integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== - -"@webassemblyjs/helper-buffer@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" - integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== - -"@webassemblyjs/helper-numbers@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" - integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.13.2" - "@webassemblyjs/helper-api-error" "1.13.2" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" - integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== - -"@webassemblyjs/helper-wasm-section@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" - integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/wasm-gen" "1.14.1" - -"@webassemblyjs/ieee754@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" - integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" - integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" - integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== - -"@webassemblyjs/wasm-edit@^1.12.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" - integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/helper-wasm-section" "1.14.1" - "@webassemblyjs/wasm-gen" "1.14.1" - "@webassemblyjs/wasm-opt" "1.14.1" - "@webassemblyjs/wasm-parser" "1.14.1" - "@webassemblyjs/wast-printer" "1.14.1" - -"@webassemblyjs/wasm-gen@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" - integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/ieee754" "1.13.2" - "@webassemblyjs/leb128" "1.13.2" - "@webassemblyjs/utf8" "1.13.2" - -"@webassemblyjs/wasm-opt@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" - integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/wasm-gen" "1.14.1" - "@webassemblyjs/wasm-parser" "1.14.1" - -"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.12.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" - integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-api-error" "1.13.2" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/ieee754" "1.13.2" - "@webassemblyjs/leb128" "1.13.2" - "@webassemblyjs/utf8" "1.13.2" - -"@webassemblyjs/wast-printer@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" - integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@xtuc/long" "4.2.2" - -"@webpack-cli/configtest@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz#3b2f852e91dac6e3b85fb2a314fb8bef46d94646" - integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== - -"@webpack-cli/info@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.2.tgz#cc3fbf22efeb88ff62310cf885c5b09f44ae0fdd" - integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== - -"@webpack-cli/serve@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e" - integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -accepts@~1.3.4: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-import-attributes@^1.9.5: - version "1.9.5" - resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" - integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== - -acorn@^8.15.0, acorn@^8.7.1: - version "8.18.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.18.0.tgz#4faf01b2d6d326bfeed97aea1f52220b5f4c1940" - integrity sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ== - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv-keywords@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== - dependencies: - fast-deep-equal "^3.1.3" - -ajv@^6.12.5: - version "6.15.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492" - integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.0, ajv@^8.9.0: - version "8.20.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.20.0.tgz#304b3636add88ba7d936760dd50ece006dea95f9" - integrity sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA== - dependencies: - fast-deep-equal "^3.1.3" - fast-uri "^3.0.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - -ansi-colors@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" - integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64id@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" - integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== - -baseline-browser-mapping@^2.11.20: - version "2.11.21" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz#99af73cb8e54007e4f5345e132278e26c2662f2c" - integrity sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ== - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -body-parser@^1.19.0: - version "1.20.6" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.6.tgz#60c789c78e0992d906da0a29d71ae01d15c1ed76" - integrity sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g== - dependencies: - bytes "~3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "~1.2.0" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - on-finished "~2.4.1" - qs "~6.15.1" - raw-body "~2.5.3" - type-is "~1.6.18" - unpipe "~1.0.0" - -brace-expansion@^1.1.7: - version "1.1.18" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.18.tgz#3ce74d89885136be1535341f8c3d4425c29a5cab" - integrity sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1, brace-expansion@^2.0.2: - version "2.1.4" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.1.4.tgz#589dab11c0018d0366be64cd8bf12c8dbecc8326" - integrity sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browser-stdout@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -browserslist@^4.21.10: - version "4.28.9" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.9.tgz#07ce6b449b90af880eb9bfb7cd39372cc4f71c8c" - integrity sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg== - dependencies: - baseline-browser-mapping "^2.11.20" - caniuse-lite "^1.0.30001810" - electron-to-chromium "^1.5.420" - node-releases "^2.0.54" - update-browserslist-db "^1.3.2" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -bytes@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bound@^1.0.2, call-bound@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" - -camelcase@^6.0.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001810: - version "1.0.30001810" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz#4970b477dea3278374de9bc43aa8f5d39fc3cda2" - integrity sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg== - -chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chokidar@^3.5.1, chokidar@^3.5.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chrome-trace-event@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" - integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colorette@^2.0.14: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -connect@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" - integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== - dependencies: - debug "2.6.9" - finalhandler "1.1.2" - parseurl "~1.3.3" - utils-merge "1.0.1" - -content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -cookie@~0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" - integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== - -cors@~2.8.5: - version "2.8.6" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96" - integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw== - dependencies: - object-assign "^4" - vary "^1" - -cross-spawn@^7.0.3: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -custom-event@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" - integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg== - -date-format@^4.0.14: - version "4.0.14" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400" - integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg== - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^4.3.4, debug@^4.3.5, debug@~4.4.1: - version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - -decamelize@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" - integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== - -depd@2.0.0, depd@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -di@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" - integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA== - -diff@^5.2.0: - version "5.2.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.2.tgz#0a4742797281d09cfa699b79ea32d27723623bad" - integrity sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A== - -dom-serialize@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" - integrity sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ== - dependencies: - custom-event "~1.0.0" - ent "~2.2.0" - extend "^3.0.0" - void-elements "^2.0.0" - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.5.420: - version "1.5.422" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.422.tgz#e27fb1ca0e6bef612a647022e1469701fea9ee1f" - integrity sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -engine.io-parser@~5.2.1: - version "5.2.3" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" - integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== - -engine.io@~6.6.0: - version "6.6.10" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.6.10.tgz#67822e942929c10c5918fb6fb7c71bde0fe01ef3" - integrity sha512-9/lX2bdlizlCXMHRMOIm03VBQHQYC7VvydcxtTAUJRxNW1QzM/2PMFSmr6h/lCiMHcyCP6abK+t9Q+j4vekk8Q== - dependencies: - "@types/cors" "^2.8.12" - "@types/node" ">=10.0.0" - "@types/ws" "^8.5.12" - accepts "~1.3.4" - cookie "~0.7.2" - cors "~2.8.5" - debug "~4.4.1" - engine.io-parser "~5.2.1" - ws "~8.21.0" - -enhanced-resolve@^5.17.1: - version "5.24.5" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz#b4dad3255b7545f07ba5535189868e9f85f47573" - integrity sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.3.3" - -ent@~2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.2.tgz#22a5ed2fd7ce0cbcff1d1474cf4909a44bdb6e85" - integrity sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw== - dependencies: - call-bound "^1.0.3" - es-errors "^1.3.0" - punycode "^1.4.1" - safe-regex-test "^1.1.0" - -envinfo@^7.7.3: - version "7.21.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.21.0.tgz#04a251be79f92548541f37d13c8b6f22940c3bae" - integrity sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow== - -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-module-lexer@^1.2.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" - integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz#a2d0b373205724dfa525d23b0c3e1b1ca582c99b" - integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw== - dependencies: - es-errors "^1.3.0" - -escalade@^3.1.1, escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -extend@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-uri@^3.0.1: - version "3.1.7" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.7.tgz#743157d957f3cbb4c65310e033dc2ad4ad7dc60a" - integrity sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg== - -fastest-levenshtein@^1.0.12: - version "1.0.16" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" - integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -flatted@^3.2.7: - version "3.4.4" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.4.tgz#aeeca2a506303f0cee61c59e6c9f2a88d2f29fc6" - integrity sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q== - -follow-redirects@^1.0.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" - integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== - -format-util@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" - integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== - -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@^7.1.3, glob@^7.1.7: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-symbols@^1.0.3, has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -hasown@^2.0.2, hasown@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" - integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== - dependencies: - function-bind "^1.1.2" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -http-errors@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" - integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== - dependencies: - depd "~2.0.0" - inherits "~2.0.4" - setprototypeof "~1.2.0" - statuses "~2.0.2" - toidentifier "~1.0.1" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -iconv-lite@~0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -import-local@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" - integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@~2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -interpret@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" - integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-core-module@^2.16.1: - version "2.16.2" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.2.tgz#3e07450a8080ebce3fbf0cac494f4d2ab324e082" - integrity sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA== - dependencies: - hasown "^2.0.3" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-obj@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" - integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== - dependencies: - call-bound "^1.0.2" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -isbinaryfile@^4.0.8: - version "4.0.10" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" - integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -js-yaml@^4.1.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.2.tgz#8e44fb14a2643c59726bb15787b5f1512cb3d3fb" - integrity sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA== - dependencies: - argparse "^2.0.1" - -json-parse-even-better-errors@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" - -karma-chrome-launcher@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz#eb9c95024f2d6dfbb3748d3415ac9b381906b9a9" - integrity sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q== - dependencies: - which "^1.2.1" - -karma-mocha@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-2.0.1.tgz#4b0254a18dfee71bdbe6188d9a6861bf86b0cd7d" - integrity sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ== - dependencies: - minimist "^1.2.3" - -karma-sourcemap-loader@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.4.0.tgz#b01d73f8f688f533bcc8f5d273d43458e13b5488" - integrity sha512-xCRL3/pmhAYF3I6qOrcn0uhbQevitc2DERMPH82FMnG+4WReoGcGFZb1pURf2a5apyrOHRdvD+O6K7NljqKHyA== - dependencies: - graceful-fs "^4.2.10" - -karma-webpack@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.1.tgz#4eafd31bbe684a747a6e8f3e4ad373e53979ced4" - integrity sha512-oo38O+P3W2mSPCSUrQdySSPv1LvPpXP+f+bBimNomS5sW+1V4SuhCuW8TfJzV+rDv921w2fDSDw0xJbPe6U+kQ== - dependencies: - glob "^7.1.3" - minimatch "^9.0.3" - webpack-merge "^4.1.5" - -karma@6.4.4: - version "6.4.4" - resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.4.tgz#dfa5a426cf5a8b53b43cd54ef0d0d09742351492" - integrity sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w== - dependencies: - "@colors/colors" "1.5.0" - body-parser "^1.19.0" - braces "^3.0.2" - chokidar "^3.5.1" - connect "^3.7.0" - di "^0.0.1" - dom-serialize "^2.2.1" - glob "^7.1.7" - graceful-fs "^4.2.6" - http-proxy "^1.18.1" - isbinaryfile "^4.0.8" - lodash "^4.17.21" - log4js "^6.4.1" - mime "^2.5.2" - minimatch "^3.0.4" - mkdirp "^0.5.5" - qjobs "^1.2.0" - range-parser "^1.2.1" - rimraf "^3.0.2" - socket.io "^4.7.2" - source-map "^0.6.1" - tmp "^0.2.1" - ua-parser-js "^0.7.30" - yargs "^16.1.1" - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kotlin-web-helpers@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kotlin-web-helpers/-/kotlin-web-helpers-2.0.0.tgz#b112096b273c1e733e0b86560998235c09a19286" - integrity sha512-xkVGl60Ygn/zuLkDPx+oHj7jeLR7hCvoNF99nhwXMn8a3ApB4lLiC9pk4ol4NHPjyoCbvQctBqvzUcp8pkqyWw== - dependencies: - format-util "^1.0.5" - -loader-runner@^4.2.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.2.tgz#9913d3a15971f8f635915e601fb5c9d495d918e9" - integrity sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash@^4.17.15, lodash@^4.17.21: - version "4.18.1" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" - integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== - -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -log4js@^6.4.1: - version "6.9.1" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz#aba5a3ff4e7872ae34f8b4c533706753709e38b6" - integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g== - dependencies: - date-format "^4.0.14" - debug "^4.3.4" - flatted "^3.2.7" - rfdc "^1.3.0" - streamroller "^3.1.5" - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@^2.5.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - -minimatch@^3.0.4, minimatch@^3.1.1: - version "3.1.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" - integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1, minimatch@^5.1.6: - version "5.1.9" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.9.tgz#1293ef15db0098b394540e8f9f744f9fda8dee4b" - integrity sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.3: - version "9.0.9" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" - integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== - dependencies: - brace-expansion "^2.0.2" - -minimist@^1.2.3, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mkdirp@^0.5.5: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -mocha@10.7.3: - version "10.7.3" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.3.tgz#ae32003cabbd52b59aece17846056a68eb4b0752" - integrity sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A== - dependencies: - ansi-colors "^4.1.3" - browser-stdout "^1.3.1" - chokidar "^3.5.3" - debug "^4.3.5" - diff "^5.2.0" - escape-string-regexp "^4.0.0" - find-up "^5.0.0" - glob "^8.1.0" - he "^1.2.0" - js-yaml "^4.1.0" - log-symbols "^4.1.0" - minimatch "^5.1.6" - ms "^2.1.3" - serialize-javascript "^6.0.2" - strip-json-comments "^3.1.1" - supports-color "^8.1.1" - workerpool "^6.5.1" - yargs "^16.2.0" - yargs-parser "^20.2.9" - yargs-unparser "^2.0.0" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -node-releases@^2.0.54: - version "2.0.54" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.54.tgz#09af17d5647aa9f221ec5cf2becb95b68a981afe" - integrity sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -object-assign@^4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.3, object-inspect@^1.13.4: - version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" - integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== - dependencies: - ee-first "1.1.1" - -on-finished@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601" - integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -qjobs@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" - integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== - -qs@~6.15.1: - version "6.15.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b" - integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A== - dependencies: - es-define-property "^1.0.1" - side-channel "^1.1.1" - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.3.0.tgz#d7f19be812bb62721472b45d3be219ef09572b47" - integrity sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw== - -raw-body@~2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" - integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== - dependencies: - bytes "~3.1.2" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - unpipe "~1.0.0" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -rechoir@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" - integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== - dependencies: - resolve "^1.20.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve@^1.20.0: - version "1.22.12" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.12.tgz#f5b2a680897c69c238a13cd16b15671f8b73549f" - integrity sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA== - dependencies: - es-errors "^1.3.0" - is-core-module "^2.16.1" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -rfdc@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" - integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -safe-buffer@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-regex-test@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" - integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - is-regex "^1.2.1" - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -schema-utils@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^4.3.0: - version "4.3.3" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" - integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - -serialize-javascript@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" - integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== - dependencies: - randombytes "^2.1.0" - -setprototypeof@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel-list@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127" - integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.4" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.1.tgz#ea02c62e05dc4bea67d4442f0fb71ee192f8e0ab" - integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.4" - side-channel-list "^1.0.1" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - -socket.io-adapter@~2.5.2: - version "2.5.8" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz#c9fcabf602dbd5b09258ca98e5ec6a7ae4360698" - integrity sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw== - dependencies: - debug "~4.4.1" - ws "~8.21.0" - -socket.io-parser@~4.2.4: - version "4.2.7" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.7.tgz#679e51fe24d1c81df90fc5f7efe4a5f432fe99c0" - integrity sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg== - dependencies: - "@socket.io/component-emitter" "~3.1.0" - debug "~4.4.1" - -socket.io@^4.7.2: - version "4.8.3" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.8.3.tgz#ca6ba1431c69532e1e0a6f496deebeb601dbc4df" - integrity sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A== - dependencies: - accepts "~1.3.4" - base64id "~2.0.0" - cors "~2.8.5" - debug "~4.4.1" - engine.io "~6.6.0" - socket.io-adapter "~2.5.2" - socket.io-parser "~4.2.4" - -source-map-js@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - -source-map-loader@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-5.0.0.tgz#f593a916e1cc54471cfc8851b905c8a845fc7e38" - integrity sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA== - dependencies: - iconv-lite "^0.6.3" - source-map-js "^1.0.2" - -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -statuses@~2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" - integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== - -streamroller@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz#1263182329a45def1ffaef58d31b15d13d2ee7ff" - integrity sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw== - dependencies: - date-format "^4.0.14" - debug "^4.3.4" - fs-extra "^8.1.0" - -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0, supports-color@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tapable@^2.1.1, tapable@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.3.tgz#5da7c9992c46038221267985ab28421a8879f160" - integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A== - -terser-webpack-plugin@^5.3.10: - version "5.6.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz#47bc41bd8b8fab8383b62ec763b7394829097e7b" - integrity sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ== - dependencies: - "@jridgewell/trace-mapping" "^0.3.25" - jest-worker "^27.4.5" - schema-utils "^4.3.0" - terser "^5.31.1" - -terser@^5.31.1: - version "5.51.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.51.2.tgz#555912425a7f2ff7737425c3718fa76c7069f6ef" - integrity sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.15.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -tmp@^0.2.1: - version "0.2.7" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.7.tgz#26f4db11d1601ce8012dcb8a798ece1c06a99059" - integrity sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typescript@5.5.4: - version "5.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" - integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== - -ua-parser-js@^0.7.30: - version "0.7.41" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.41.tgz#9f6dee58c389e8afababa62a4a2dc22edb69a452" - integrity sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg== - -undici-types@~8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-8.3.0.tgz#44e9fc9f3244648cdea35e4f9bb2d681e9410809" - integrity sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ== - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -update-browserslist-db@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz#9d99fbff56c50bb11ba5fd35cece5916da595836" - integrity sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -vary@^1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -void-elements@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" - integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung== - -watchpack@^2.4.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.2.tgz#e12e82d84674266fc1c6dbfe38891b92ff0522ec" - integrity sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg== - dependencies: - graceful-fs "^4.1.2" - -webpack-cli@5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.4.tgz#c8e046ba7eaae4911d7e71e2b25b776fcc35759b" - integrity sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^2.1.1" - "@webpack-cli/info" "^2.0.2" - "@webpack-cli/serve" "^2.0.5" - colorette "^2.0.14" - commander "^10.0.1" - cross-spawn "^7.0.3" - envinfo "^7.7.3" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^3.1.1" - rechoir "^0.8.0" - webpack-merge "^5.7.3" - -webpack-merge@^4.1.5: - version "4.2.2" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" - integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== - dependencies: - lodash "^4.17.15" - -webpack-merge@^5.7.3: - version "5.10.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" - integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== - dependencies: - clone-deep "^4.0.1" - flat "^5.0.2" - wildcard "^2.0.0" - -webpack-sources@^3.2.3: - version "3.5.1" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.5.1.tgz#76c2418486dcc02b2aa0694c104176c2858fe84a" - integrity sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw== - -webpack@5.94.0: - version "5.94.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" - integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== - dependencies: - "@types/estree" "^1.0.5" - "@webassemblyjs/ast" "^1.12.1" - "@webassemblyjs/wasm-edit" "^1.12.1" - "@webassemblyjs/wasm-parser" "^1.12.1" - acorn "^8.7.1" - acorn-import-attributes "^1.9.5" - browserslist "^4.21.10" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.1" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.11" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.2.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.3.10" - watchpack "^2.4.1" - webpack-sources "^3.2.3" - -which@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wildcard@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" - integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== - -workerpool@^6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" - integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -ws@~8.21.0: - version "8.21.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.21.3.tgz#660b4faddb6a3e575c86e078126919961f4de4fc" - integrity sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yargs-parser@^20.2.2, yargs-parser@^20.2.9: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-unparser@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" - integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== - dependencies: - camelcase "^6.0.0" - decamelize "^4.0.0" - flat "^5.0.2" - is-plain-obj "^2.1.0" - -yargs@^16.1.1, yargs@^16.2.0: - version "16.2.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.2.tgz#c56731dca0d2788ae0866dd3c83907d6bab85f7d" - integrity sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/krelay/api/android/krelay.api b/krelay/api/android/krelay.api index 148c3e3..a24e717 100644 --- a/krelay/api/android/krelay.api +++ b/krelay/api/android/krelay.api @@ -91,8 +91,10 @@ public abstract interface class dev/brewkits/krelay/KRelayInstance { public abstract fun cancelScope (Ljava/lang/String;)V public abstract fun clearQueue (Lkotlin/reflect/KClass;)V public abstract fun dispatch (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Ljava/lang/String;)V + public static synthetic fun dispatch$default (Ldev/brewkits/krelay/KRelayInstance;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Ljava/lang/String;ILjava/lang/Object;)V public abstract fun dispatchPersisted (Lkotlin/reflect/KClass;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V public abstract fun dispatchWithPriority (Lkotlin/reflect/KClass;ILkotlin/jvm/functions/Function1;Ljava/lang/String;)V + public static synthetic fun dispatchWithPriority$default (Ldev/brewkits/krelay/KRelayInstance;Lkotlin/reflect/KClass;ILkotlin/jvm/functions/Function1;Ljava/lang/String;ILjava/lang/Object;)V public abstract fun dump ()V public abstract fun getActionExpiryMs ()J public abstract fun getDebugInfo ()Ldev/brewkits/krelay/DebugInfo; @@ -113,6 +115,7 @@ public abstract interface class dev/brewkits/krelay/KRelayInstance { public abstract fun setMaxQueueSize (I)V public abstract fun setPersistenceAdapter (Ldev/brewkits/krelay/KRelayPersistenceAdapter;)V public abstract fun unregister (Lkotlin/reflect/KClass;Ldev/brewkits/krelay/RelayFeature;)V + public static synthetic fun unregister$default (Ldev/brewkits/krelay/KRelayInstance;Lkotlin/reflect/KClass;Ldev/brewkits/krelay/RelayFeature;ILjava/lang/Object;)V } public final class dev/brewkits/krelay/KRelayInstance$DefaultImpls { @@ -295,6 +298,7 @@ public abstract interface class dev/brewkits/krelay/samples/AnalyticsFeature : d public abstract fun track (Ljava/lang/String;)V public abstract fun track (Ljava/lang/String;Ljava/util/Map;)V public abstract fun trackScreen (Ljava/lang/String;Ljava/lang/String;)V + public static synthetic fun trackScreen$default (Ldev/brewkits/krelay/samples/AnalyticsFeature;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V } public final class dev/brewkits/krelay/samples/AnalyticsFeature$DefaultImpls { @@ -310,6 +314,7 @@ public final class dev/brewkits/krelay/samples/BiometricDemoViewModel { public abstract interface class dev/brewkits/krelay/samples/BiometricFeature : dev/brewkits/krelay/RelayFeature { public abstract fun authenticate (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun authenticate$default (Ldev/brewkits/krelay/samples/BiometricFeature;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public abstract fun authenticateSimple (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V public abstract fun isAvailable ()Z } @@ -352,9 +357,11 @@ public abstract interface class dev/brewkits/krelay/samples/EnhancedMediaFeature public abstract interface class dev/brewkits/krelay/samples/HapticFeature : dev/brewkits/krelay/RelayFeature { public abstract fun error ()V public abstract fun impact (Ldev/brewkits/krelay/samples/HapticStyle;)V + public static synthetic fun impact$default (Ldev/brewkits/krelay/samples/HapticFeature;Ldev/brewkits/krelay/samples/HapticStyle;ILjava/lang/Object;)V public abstract fun selection ()V public abstract fun success ()V public abstract fun vibrate (J)V + public static synthetic fun vibrate$default (Ldev/brewkits/krelay/samples/HapticFeature;JILjava/lang/Object;)V public abstract fun warning ()V } @@ -407,8 +414,10 @@ public abstract interface class dev/brewkits/krelay/samples/MediaFeature : dev/b public abstract fun isGalleryAvailable ()Z public abstract fun pickImageFromGallery (Lkotlin/jvm/functions/Function1;)V public abstract fun pickMultipleImages (ILkotlin/jvm/functions/Function1;)V + public static synthetic fun pickMultipleImages$default (Ldev/brewkits/krelay/samples/MediaFeature;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public abstract fun pickVideo (Lkotlin/jvm/functions/Function1;)V public abstract fun recordVideo (ILkotlin/jvm/functions/Function1;)V + public static synthetic fun recordVideo$default (Ldev/brewkits/krelay/samples/MediaFeature;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)V } public final class dev/brewkits/krelay/samples/MediaFeature$DefaultImpls { @@ -439,6 +448,7 @@ public final class dev/brewkits/krelay/samples/MediaResult { public abstract interface class dev/brewkits/krelay/samples/NavigationFeature : dev/brewkits/krelay/RelayFeature { public abstract fun navigateBack ()V public abstract fun navigateTo (Ljava/lang/String;Ljava/util/Map;)V + public static synthetic fun navigateTo$default (Ldev/brewkits/krelay/samples/NavigationFeature;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V public abstract fun navigateToRoot ()V } @@ -448,6 +458,7 @@ public final class dev/brewkits/krelay/samples/NavigationFeature$DefaultImpls { public abstract interface class dev/brewkits/krelay/samples/NotificationBridge : dev/brewkits/krelay/RelayFeature { public abstract fun showInAppNotification (Ljava/lang/String;Ljava/lang/String;I)V + public static synthetic fun showInAppNotification$default (Ldev/brewkits/krelay/samples/NotificationBridge;Ljava/lang/String;Ljava/lang/String;IILjava/lang/Object;)V public abstract fun showSystemNotification (Ljava/lang/String;Ljava/lang/String;)V } @@ -488,9 +499,12 @@ public final class dev/brewkits/krelay/samples/SystemInteractionDemoViewModel { public abstract interface class dev/brewkits/krelay/samples/SystemInteractionFeature : dev/brewkits/krelay/RelayFeature { public abstract fun checkForAppUpdates (Lkotlin/jvm/functions/Function1;)V + public static synthetic fun checkForAppUpdates$default (Ldev/brewkits/krelay/samples/SystemInteractionFeature;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public abstract fun openAppSettings ()V public abstract fun openAppStorePage (Z)V + public static synthetic fun openAppStorePage$default (Ldev/brewkits/krelay/samples/SystemInteractionFeature;ZILjava/lang/Object;)V public abstract fun promptUpdate (ZLkotlin/jvm/functions/Function1;)V + public static synthetic fun promptUpdate$default (Ldev/brewkits/krelay/samples/SystemInteractionFeature;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public abstract fun requestInAppReview ()V public abstract fun shareApp ()V } diff --git a/krelay/api/jvm/krelay.api b/krelay/api/jvm/krelay.api index 5f85222..9b1df72 100644 --- a/krelay/api/jvm/krelay.api +++ b/krelay/api/jvm/krelay.api @@ -91,8 +91,10 @@ public abstract interface class dev/brewkits/krelay/KRelayInstance { public abstract fun cancelScope (Ljava/lang/String;)V public abstract fun clearQueue (Lkotlin/reflect/KClass;)V public abstract fun dispatch (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Ljava/lang/String;)V + public static synthetic fun dispatch$default (Ldev/brewkits/krelay/KRelayInstance;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Ljava/lang/String;ILjava/lang/Object;)V public abstract fun dispatchPersisted (Lkotlin/reflect/KClass;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V public abstract fun dispatchWithPriority (Lkotlin/reflect/KClass;ILkotlin/jvm/functions/Function1;Ljava/lang/String;)V + public static synthetic fun dispatchWithPriority$default (Ldev/brewkits/krelay/KRelayInstance;Lkotlin/reflect/KClass;ILkotlin/jvm/functions/Function1;Ljava/lang/String;ILjava/lang/Object;)V public abstract fun dump ()V public abstract fun getActionExpiryMs ()J public abstract fun getDebugInfo ()Ldev/brewkits/krelay/DebugInfo; @@ -113,6 +115,7 @@ public abstract interface class dev/brewkits/krelay/KRelayInstance { public abstract fun setMaxQueueSize (I)V public abstract fun setPersistenceAdapter (Ldev/brewkits/krelay/KRelayPersistenceAdapter;)V public abstract fun unregister (Lkotlin/reflect/KClass;Ldev/brewkits/krelay/RelayFeature;)V + public static synthetic fun unregister$default (Ldev/brewkits/krelay/KRelayInstance;Lkotlin/reflect/KClass;Ldev/brewkits/krelay/RelayFeature;ILjava/lang/Object;)V } public final class dev/brewkits/krelay/KRelayInstance$DefaultImpls { @@ -279,6 +282,7 @@ public abstract interface class dev/brewkits/krelay/samples/AnalyticsFeature : d public abstract fun track (Ljava/lang/String;)V public abstract fun track (Ljava/lang/String;Ljava/util/Map;)V public abstract fun trackScreen (Ljava/lang/String;Ljava/lang/String;)V + public static synthetic fun trackScreen$default (Ldev/brewkits/krelay/samples/AnalyticsFeature;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V } public final class dev/brewkits/krelay/samples/AnalyticsFeature$DefaultImpls { @@ -294,6 +298,7 @@ public final class dev/brewkits/krelay/samples/BiometricDemoViewModel { public abstract interface class dev/brewkits/krelay/samples/BiometricFeature : dev/brewkits/krelay/RelayFeature { public abstract fun authenticate (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun authenticate$default (Ldev/brewkits/krelay/samples/BiometricFeature;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public abstract fun authenticateSimple (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V public abstract fun isAvailable ()Z } @@ -336,9 +341,11 @@ public abstract interface class dev/brewkits/krelay/samples/EnhancedMediaFeature public abstract interface class dev/brewkits/krelay/samples/HapticFeature : dev/brewkits/krelay/RelayFeature { public abstract fun error ()V public abstract fun impact (Ldev/brewkits/krelay/samples/HapticStyle;)V + public static synthetic fun impact$default (Ldev/brewkits/krelay/samples/HapticFeature;Ldev/brewkits/krelay/samples/HapticStyle;ILjava/lang/Object;)V public abstract fun selection ()V public abstract fun success ()V public abstract fun vibrate (J)V + public static synthetic fun vibrate$default (Ldev/brewkits/krelay/samples/HapticFeature;JILjava/lang/Object;)V public abstract fun warning ()V } @@ -391,8 +398,10 @@ public abstract interface class dev/brewkits/krelay/samples/MediaFeature : dev/b public abstract fun isGalleryAvailable ()Z public abstract fun pickImageFromGallery (Lkotlin/jvm/functions/Function1;)V public abstract fun pickMultipleImages (ILkotlin/jvm/functions/Function1;)V + public static synthetic fun pickMultipleImages$default (Ldev/brewkits/krelay/samples/MediaFeature;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public abstract fun pickVideo (Lkotlin/jvm/functions/Function1;)V public abstract fun recordVideo (ILkotlin/jvm/functions/Function1;)V + public static synthetic fun recordVideo$default (Ldev/brewkits/krelay/samples/MediaFeature;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)V } public final class dev/brewkits/krelay/samples/MediaFeature$DefaultImpls { @@ -423,6 +432,7 @@ public final class dev/brewkits/krelay/samples/MediaResult { public abstract interface class dev/brewkits/krelay/samples/NavigationFeature : dev/brewkits/krelay/RelayFeature { public abstract fun navigateBack ()V public abstract fun navigateTo (Ljava/lang/String;Ljava/util/Map;)V + public static synthetic fun navigateTo$default (Ldev/brewkits/krelay/samples/NavigationFeature;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V public abstract fun navigateToRoot ()V } @@ -432,6 +442,7 @@ public final class dev/brewkits/krelay/samples/NavigationFeature$DefaultImpls { public abstract interface class dev/brewkits/krelay/samples/NotificationBridge : dev/brewkits/krelay/RelayFeature { public abstract fun showInAppNotification (Ljava/lang/String;Ljava/lang/String;I)V + public static synthetic fun showInAppNotification$default (Ldev/brewkits/krelay/samples/NotificationBridge;Ljava/lang/String;Ljava/lang/String;IILjava/lang/Object;)V public abstract fun showSystemNotification (Ljava/lang/String;Ljava/lang/String;)V } @@ -472,9 +483,12 @@ public final class dev/brewkits/krelay/samples/SystemInteractionDemoViewModel { public abstract interface class dev/brewkits/krelay/samples/SystemInteractionFeature : dev/brewkits/krelay/RelayFeature { public abstract fun checkForAppUpdates (Lkotlin/jvm/functions/Function1;)V + public static synthetic fun checkForAppUpdates$default (Ldev/brewkits/krelay/samples/SystemInteractionFeature;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public abstract fun openAppSettings ()V public abstract fun openAppStorePage (Z)V + public static synthetic fun openAppStorePage$default (Ldev/brewkits/krelay/samples/SystemInteractionFeature;ZILjava/lang/Object;)V public abstract fun promptUpdate (ZLkotlin/jvm/functions/Function1;)V + public static synthetic fun promptUpdate$default (Ldev/brewkits/krelay/samples/SystemInteractionFeature;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public abstract fun requestInAppReview ()V public abstract fun shareApp ()V } From 167e435e6643164aac02991f044e8cbfbf7ceff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?= Date: Thu, 1 Oct 2026 00:08:25 +0700 Subject: [PATCH 2/8] ci: fix release workflow and bump actions - github-release now needs validate so version/notes outputs resolve - move publishing to the Central Portal OSSRH staging API (s01 is retired) and hand the staged repo over after upload - bump checkout/setup-java/setup-gradle/upload-artifact/action-gh-release to Node 24-ready majors --- .github/workflows/ci.yml | 36 ++++++++++----------- .github/workflows/release.yml | 56 ++++++++++++++++++++------------- krelay-bom/build.gradle.kts | 4 +-- krelay-compose/build.gradle.kts | 4 +-- krelay-flow/build.gradle.kts | 4 +-- krelay-testing/build.gradle.kts | 4 +-- krelay/build.gradle.kts | 4 +-- 7 files changed, 62 insertions(+), 50 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fea9d2..914a061 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,12 +13,12 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - - uses: gradle/actions/setup-gradle@v3 + - uses: gradle/actions/setup-gradle@v6 - name: Run Android Lint (krelay) run: ./gradlew :krelay:lintDebug --no-daemon @@ -27,12 +27,12 @@ jobs: name: Unit Tests (JVM) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - - uses: gradle/actions/setup-gradle@v3 + - uses: gradle/actions/setup-gradle@v6 - name: Run krelay unit tests run: ./gradlew :krelay:testDebugUnitTest --no-daemon - name: Run krelay-flow unit tests @@ -41,7 +41,7 @@ jobs: run: ./gradlew :krelay-testing:testDebugUnitTest --no-daemon - name: Upload test results if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: android-test-results path: | @@ -54,17 +54,17 @@ jobs: name: Unit Tests (iOS Simulator) runs-on: macos-14 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - - uses: gradle/actions/setup-gradle@v3 + - uses: gradle/actions/setup-gradle@v6 - name: Run krelay iOS simulator tests run: ./gradlew :krelay:iosSimulatorArm64Test --no-daemon - name: Upload test results if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ios-test-results path: krelay/build/reports/ @@ -75,12 +75,12 @@ jobs: runs-on: macos-14 needs: [ lint, test-android, test-ios ] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - - uses: gradle/actions/setup-gradle@v3 + - uses: gradle/actions/setup-gradle@v6 - name: Build krelay (all targets) run: ./gradlew :krelay:assemble --no-daemon - name: Build krelay-flow (all targets) @@ -98,7 +98,7 @@ jobs: - name: Generate Dokka docs run: ./gradlew :krelay:dokkaHtmlCustom --no-daemon - name: Upload build artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: krelay-artifacts path: krelay/build/outputs/ @@ -110,12 +110,12 @@ jobs: needs: [ build ] if: github.ref == 'refs/heads/main' && github.event_name == 'push' steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - - uses: gradle/actions/setup-gradle@v3 + - uses: gradle/actions/setup-gradle@v6 - name: Publish krelay to OSSRH run: ./gradlew :krelay:publishAllPublicationsToOSSRHRepository --no-daemon env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 373eeea..1bde5f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: outputs: version: ${{ steps.parse.outputs.version }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Parse version from tag id: parse run: | @@ -42,17 +42,17 @@ jobs: runs-on: ubuntu-latest needs: validate steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - - uses: gradle/actions/setup-gradle@v3 + - uses: gradle/actions/setup-gradle@v6 - name: Run unit tests (Android & JVM & Wasm) run: ./gradlew :krelay:testDebugUnitTest :krelay:jvmTest :krelay:wasmJsBrowserTest :krelay-flow:testDebugUnitTest :krelay-flow:jvmTest :krelay-flow:wasmJsBrowserTest --no-daemon - name: Upload test results if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: jvm-wasm-test-results path: | @@ -65,17 +65,17 @@ jobs: runs-on: macos-14 needs: validate steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - - uses: gradle/actions/setup-gradle@v3 + - uses: gradle/actions/setup-gradle@v6 - name: Run iOS simulator tests run: ./gradlew :krelay:iosSimulatorArm64Test --no-daemon - name: Upload test results if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ios-test-results path: krelay/build/reports/ @@ -86,12 +86,12 @@ jobs: runs-on: ubuntu-latest needs: validate steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - - uses: gradle/actions/setup-gradle@v3 + - uses: gradle/actions/setup-gradle@v6 - name: Check public API is unchanged run: ./gradlew :krelay:apiCheck :krelay-compose:apiCheck --no-daemon @@ -101,12 +101,12 @@ jobs: runs-on: macos-14 needs: [ test-jvm, test-ios, api-check ] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - - uses: gradle/actions/setup-gradle@v3 + - uses: gradle/actions/setup-gradle@v6 - name: Build krelay run: ./gradlew :krelay:assemble --no-daemon - name: Build krelay-flow @@ -122,7 +122,7 @@ jobs: - name: Generate Dokka docs run: ./gradlew :krelay:dokkaHtmlCustom --no-daemon - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: release-artifacts path: | @@ -136,12 +136,12 @@ jobs: needs: build environment: maven-central steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - - uses: gradle/actions/setup-gradle@v3 + - uses: gradle/actions/setup-gradle@v6 - name: Publish krelay run: ./gradlew :krelay:publishAllPublicationsToOSSRHRepository --no-daemon @@ -183,13 +183,25 @@ jobs: SIGNING_KEY: ${{ secrets.SIGNING_KEY }} SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} + # Legacy OSSRH (s01) was retired; uploads now land in the Central Portal + # staging API and must be handed over to the Portal to be published. + - name: Hand staged repository over to Central Portal + run: | + AUTH=$(printf '%s:%s' "$OSSRH_USERNAME" "$OSSRH_PASSWORD" | base64) + curl --fail-with-body -X POST \ + -H "Authorization: Bearer $AUTH" \ + "https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/dev.brewkits?publishing_type=automatic" + env: + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + # ────────────────────────────────────────────────────────────────────────── github-release: name: Create GitHub Release runs-on: ubuntu-latest - needs: publish + needs: [ validate, publish ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Extract release notes from CHANGELOG.md id: changelog run: | @@ -200,7 +212,7 @@ jobs: echo "$NOTES" >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: tag_name: ${{ github.ref_name }} name: "KRelay ${{ needs.validate.outputs.version }}" diff --git a/krelay-bom/build.gradle.kts b/krelay-bom/build.gradle.kts index fddfc4e..1214206 100644 --- a/krelay-bom/build.gradle.kts +++ b/krelay-bom/build.gradle.kts @@ -62,8 +62,8 @@ publishing { maven { name = "OSSRH" - val releasesRepoUrl = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") - val snapshotsRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") + val releasesRepoUrl = uri("https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/") + val snapshotsRepoUrl = uri("https://central.sonatype.com/repository/maven-snapshots/") url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl credentials { username = findProperty("ossrhUsername")?.toString() ?: System.getenv("OSSRH_USERNAME") diff --git a/krelay-compose/build.gradle.kts b/krelay-compose/build.gradle.kts index a9a2cfd..e1cb406 100644 --- a/krelay-compose/build.gradle.kts +++ b/krelay-compose/build.gradle.kts @@ -116,8 +116,8 @@ publishing { maven { name = "OSSRH" - val releasesRepoUrl = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") - val snapshotsRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") + val releasesRepoUrl = uri("https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/") + val snapshotsRepoUrl = uri("https://central.sonatype.com/repository/maven-snapshots/") url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl credentials { diff --git a/krelay-flow/build.gradle.kts b/krelay-flow/build.gradle.kts index ae235b5..a375511 100644 --- a/krelay-flow/build.gradle.kts +++ b/krelay-flow/build.gradle.kts @@ -116,8 +116,8 @@ publishing { maven { name = "OSSRH" - val releasesRepoUrl = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") - val snapshotsRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") + val releasesRepoUrl = uri("https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/") + val snapshotsRepoUrl = uri("https://central.sonatype.com/repository/maven-snapshots/") url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl credentials { diff --git a/krelay-testing/build.gradle.kts b/krelay-testing/build.gradle.kts index 8714db3..20626a9 100644 --- a/krelay-testing/build.gradle.kts +++ b/krelay-testing/build.gradle.kts @@ -101,8 +101,8 @@ publishing { maven { name = "OSSRH" - val releasesRepoUrl = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") - val snapshotsRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") + val releasesRepoUrl = uri("https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/") + val snapshotsRepoUrl = uri("https://central.sonatype.com/repository/maven-snapshots/") url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl credentials { username = findProperty("ossrhUsername")?.toString() ?: System.getenv("OSSRH_USERNAME") diff --git a/krelay/build.gradle.kts b/krelay/build.gradle.kts index f172b1b..4eceff5 100644 --- a/krelay/build.gradle.kts +++ b/krelay/build.gradle.kts @@ -167,8 +167,8 @@ publishing { // Maven Central (Sonatype OSSRH) maven { name = "OSSRH" - val releasesRepoUrl = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") - val snapshotsRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") + val releasesRepoUrl = uri("https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/") + val snapshotsRepoUrl = uri("https://central.sonatype.com/repository/maven-snapshots/") url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl credentials { From b1b2c565e8ddf38449443e8b8496886cadbb21b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?= Date: Thu, 1 Oct 2026 00:16:35 +0700 Subject: [PATCH 3/8] feat(v2.3.0): structured logging, metrics reporter, dispatchPersistedSuspend - KRelayLog / KRelayLogSink / KRelayLogLevel for pluggable structured logging - KRelayMetricsReporter + KRelayMetricEvent for exporting metrics to external sinks - krelay-flow: dispatchPersistedSuspend - fix per-instance metrics attribution (scopeName was never passed) and record expiry - bump all modules to 2.3.0; update CHANGELOG, ROADMAP, docs/MONITORING.md --- CHANGELOG.md | 19 +++ README.md | 2 +- ROADMAP.md | 20 +-- docs/MONITORING.md | 63 ++++++++++ krelay-bom/build.gradle.kts | 2 +- krelay-compose/build.gradle.kts | 2 +- krelay-flow/build.gradle.kts | 2 +- .../brewkits/krelay/flow/PersistedSuspend.kt | 36 ++++++ .../brewkits/krelay/flow/KRelayFlowTest.kt | 32 +++++ krelay-testing/build.gradle.kts | 2 +- krelay/api/android/krelay.api | 63 ++++++++++ krelay/api/jvm/krelay.api | 63 ++++++++++ krelay/build.gradle.kts | 2 +- .../dev/brewkits/krelay/KRelayInstanceImpl.kt | 42 ++++--- .../kotlin/dev/brewkits/krelay/KRelayLog.kt | 67 ++++++++++ .../kotlin/dev/brewkits/krelay/Metrics.kt | 36 ++++++ .../dev/brewkits/krelay/MetricsReporter.kt | 35 ++++++ .../dev/brewkits/krelay/unit/KRelayLogTest.kt | 94 ++++++++++++++ .../krelay/unit/MetricsReporterTest.kt | 118 ++++++++++++++++++ 19 files changed, 671 insertions(+), 29 deletions(-) create mode 100644 docs/MONITORING.md create mode 100644 krelay-flow/src/commonMain/kotlin/dev/brewkits/krelay/flow/PersistedSuspend.kt create mode 100644 krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayLog.kt create mode 100644 krelay/src/commonMain/kotlin/dev/brewkits/krelay/MetricsReporter.kt create mode 100644 krelay/src/commonTest/kotlin/dev/brewkits/krelay/unit/KRelayLogTest.kt create mode 100644 krelay/src/commonTest/kotlin/dev/brewkits/krelay/unit/MetricsReporterTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 355c23a..7e57d95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [2.3.0] - 2026-10-01 + +### Added +- **Structured logging**: `KRelayLog` with `KRelayLogLevel` (`DEBUG`/`INFO`/`WARN`/`ERROR`), a pluggable `KRelayLogSink` and a `minLevel` filter. Route KRelay output to Logcat, os_log, Timber, Datadog, etc. The default `ConsoleLogSink` prints to the console as before. A throwing sink never breaks dispatching. +- **Metrics reporter**: `KRelayMetricsReporter` + `KRelayMetricEvent` / `KRelayMetricType`, registered via `KRelayMetrics.addReporter()` / `removeReporter()`. Receives dispatch, queue, replay, expiry and clear events for forwarding to Firebase Performance, Datadog or any custom backend. The core module stays dependency-free — no SDK exporters are bundled; see `docs/MONITORING.md`. +- **`dispatchPersistedSuspend`** (`krelay-flow`): suspend overload of `dispatchPersisted` that runs factory lookup, queueing and the persistence write on `Dispatchers.Default`, so storage I/O never blocks the calling coroutine. + +### Fixed +- **Per-instance metrics were never populated**: `KRelayInstanceImpl` recorded metrics without its `scopeName`, so `KRelayMetrics.getInstanceMetrics(scope)` always returned an empty map. Dispatch, queue, replay and clear events are now attributed to their instance. +- **Expired actions were never counted**: `recordExpiry` was never called; expired actions dropped on replay are now recorded. + +### Changed +- **Toolchain**: Kotlin 2.1.0 → 2.3.21, Compose Multiplatform 1.8.0-alpha03 → 1.10.3, Decompose 3.4.0-alpha03 → 3.5.0. Public API dumps gained only compiler-generated synthetic `$default` entries. +- **Security**: the stale JS `kotlin-js-store/yarn.lock` was removed; the wasm toolchain no longer resolves the vulnerable `qs`, `serialize-javascript` and `webpack` versions flagged by Dependabot (dev/test-only, never shipped). +- **Warnings** are now logged as `[KRelay][scope][WARN] …` (previously `[KRelay][scope] [WARN] …`). +- **CI/CD**: Maven Central publishing moved to the Central Portal OSSRH staging API (`s01.oss.sonatype.org` is retired); `github-release` now declares `needs: validate`; GitHub Actions bumped to Node 24-ready majors. + +--- + ## [2.2.0] - 2026-09-05 ### Added diff --git a/README.md b/README.md index 1ec87bf..ed74e32 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ KRelay now provides a **Bill of Materials (BOM)** to automatically align version sourceSets { commonMain.dependencies { // 1. (Recommended) Import the BOM - api(platform("dev.brewkits:krelay-bom:2.2.0")) + api(platform("dev.brewkits:krelay-bom:2.3.0")) // 2. Add dependencies without specifying versions implementation("dev.brewkits:krelay") diff --git a/ROADMAP.md b/ROADMAP.md index 2898a3c..6c31fa5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -57,14 +57,20 @@ --- +### v2.3.0 — Observability & Ecosystem *(Oct 2026)* +- **Structured logging**: `KRelayLog` with severity levels and pluggable `KRelayLogSink` +- **Metrics reporter**: `KRelayMetricsReporter` for exporting to Firebase Performance, Datadog or custom sinks +- **`dispatchPersistedSuspend`** in `krelay-flow` +- Fixed per-instance metrics attribution and expiry counting +- Kotlin 2.3.21 / Compose Multiplatform 1.10.3 + +--- + ## šŸ”­ Planned -### v2.3.0 — Observability & Ecosystem *(Q1 2027)* -- `KRelayMetrics` reporter: export to Firebase Performance, Datadog, or custom sinks -- **`dispatchPersistedSuspend`**: Suspend overload for `dispatchPersisted`, callable from coroutine scopes -- **Library-specific integration modules** (community-driven): `krelay-moko`, `krelay-voyager` -- Structured logging with configurable severity and sinks -- Integration guide with popular monitoring tools +### Community-driven +- **Library-specific integration modules**: `krelay-moko`, `krelay-voyager` — contributions welcome +- Ready-made Firebase / Datadog reporters as separate opt-in artifacts (core stays dependency-free) ### v3.0.0 — TBD *(2027+)* No breaking changes are currently planned. If a v3 happens, a full migration guide will be provided well in advance. @@ -91,4 +97,4 @@ Issues, PRs, and discussions are welcome at [github.com/brewkits/KRelay](https:/ --- -**Current Version**: v2.2.0 Ā· **Last Updated**: 2026-09-05 +**Current Version**: v2.3.0 Ā· **Last Updated**: 2026-10-01 diff --git a/docs/MONITORING.md b/docs/MONITORING.md new file mode 100644 index 0000000..f47f236 --- /dev/null +++ b/docs/MONITORING.md @@ -0,0 +1,63 @@ +# Monitoring & Logging (v2.3.0+) + +KRelay exposes two small hooks. Neither pulls in a third-party SDK — you forward the data to whichever +backend you use (Firebase Performance, Datadog, Sentry, your own endpoint…). + +## Structured logging + +```kotlin +// Only surface warnings and errors in production +KRelayLog.minLevel = KRelayLogLevel.WARN + +KRelayLog.sink = KRelayLogSink { level, scopeName, message -> + // Forward to your logger of choice + MyLogger.log(level.name, tag = "KRelay[$scopeName]", message = message) +} +``` + +- Levels: `DEBUG`, `INFO`, `WARN`, `ERROR`. +- `DEBUG` and most `WARN` records are only produced when the instance was built with `debugMode(true)`. + `ERROR` records (for example an action that threw) are always produced. +- Sinks can be called from any thread and must be fast. Exceptions thrown by a sink are swallowed. +- `KRelayLog.reset()` restores the console sink and `DEBUG` level. + +## Metrics reporter + +Metrics are opt-in: + +```kotlin +KRelayMetrics.enabled = true + +KRelayMetrics.addReporter { event -> + // event.type: DISPATCH | QUEUE | REPLAY | EXPIRY | CLEAR + // event.featureName: e.g. "ToastFeature" + // event.scopeName: instance scope, "" for the default singleton + // event.count: number of actions covered by the event + MyAnalytics.count( + name = "krelay.${event.type.name.lowercase()}", + value = event.count, + tags = mapOf("feature" to event.featureName, "scope" to event.scopeName) + ) +} +``` + +Guidelines: + +- The reporter runs synchronously on the recording thread, and for replay/expiry events while KRelay + holds its internal (reentrant) lock. Keep it cheap and hand heavy work to another thread or batch it. +- Exceptions thrown by a reporter are swallowed. +- `removeReporter()` detaches it; `KRelayMetrics.reset()` clears counters but keeps reporters. +- Pull-style access is still available: `KRelayMetrics.getAllMetrics()` and + `KRelayMetrics.getInstanceMetrics(scopeName)`. + +`MyLogger` / `MyAnalytics` above are placeholders for your own wrappers around the monitoring SDK. + +## Persisting from coroutines + +With `krelay-flow`, `dispatchPersistedSuspend` keeps persistence I/O off the calling coroutine: + +```kotlin +viewModelScope.launch { + relay.dispatchPersistedSuspend("toast", "show", payload = "Saved") +} +``` diff --git a/krelay-bom/build.gradle.kts b/krelay-bom/build.gradle.kts index 1214206..32eb0b2 100644 --- a/krelay-bom/build.gradle.kts +++ b/krelay-bom/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } group = "dev.brewkits" -version = "2.2.0" +version = "2.3.0" // Declare BOM constraints — all KRelay artifacts aligned to the same version dependencies { diff --git a/krelay-compose/build.gradle.kts b/krelay-compose/build.gradle.kts index e1cb406..c6ad50e 100644 --- a/krelay-compose/build.gradle.kts +++ b/krelay-compose/build.gradle.kts @@ -12,7 +12,7 @@ plugins { } group = "dev.brewkits" -version = "2.2.0" +version = "2.3.0" kotlin { androidTarget { diff --git a/krelay-flow/build.gradle.kts b/krelay-flow/build.gradle.kts index a375511..842ebe0 100644 --- a/krelay-flow/build.gradle.kts +++ b/krelay-flow/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "dev.brewkits" -version = "2.2.0" +version = "2.3.0" @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class) kotlin { diff --git a/krelay-flow/src/commonMain/kotlin/dev/brewkits/krelay/flow/PersistedSuspend.kt b/krelay-flow/src/commonMain/kotlin/dev/brewkits/krelay/flow/PersistedSuspend.kt new file mode 100644 index 0000000..9a8d18f --- /dev/null +++ b/krelay-flow/src/commonMain/kotlin/dev/brewkits/krelay/flow/PersistedSuspend.kt @@ -0,0 +1,36 @@ +package dev.brewkits.krelay.flow + +import dev.brewkits.krelay.ActionPriority +import dev.brewkits.krelay.KRelayInstance +import dev.brewkits.krelay.RelayFeature +import dev.brewkits.krelay.dispatchPersisted +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** + * Suspend variant of [dispatchPersisted] for use from coroutine scopes. + * + * The factory lookup, queueing and — when no implementation is registered yet — the persistence + * write (`SharedPreferences` / `NSUserDefaults` / custom adapter) run on [Dispatchers.Default], + * so the calling coroutine (typically on `Dispatchers.Main`) is never blocked by storage I/O. + * The action itself is still delivered on the main thread, exactly as with [dispatchPersisted]. + * + * Throws `IllegalStateException` if no factory is registered for [featureKey]/[actionKey], + * like [dispatchPersisted]. + * + * ```kotlin + * viewModelScope.launch { + * relay.dispatchPersistedSuspend("toast", "show", payload = "Saved") + * } + * ``` + */ +suspend inline fun KRelayInstance.dispatchPersistedSuspend( + featureKey: String, + actionKey: String, + payload: String = "", + priority: ActionPriority = ActionPriority.DEFAULT +) { + withContext(Dispatchers.Default) { + dispatchPersisted(featureKey, actionKey, payload, priority) + } +} diff --git a/krelay-flow/src/commonTest/kotlin/dev/brewkits/krelay/flow/KRelayFlowTest.kt b/krelay-flow/src/commonTest/kotlin/dev/brewkits/krelay/flow/KRelayFlowTest.kt index 5800e20..7126648 100644 --- a/krelay-flow/src/commonTest/kotlin/dev/brewkits/krelay/flow/KRelayFlowTest.kt +++ b/krelay-flow/src/commonTest/kotlin/dev/brewkits/krelay/flow/KRelayFlowTest.kt @@ -2,6 +2,7 @@ package dev.brewkits.krelay.flow import dev.brewkits.krelay.KRelay import dev.brewkits.krelay.RelayFeature +import dev.brewkits.krelay.registerActionFactory import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn @@ -78,3 +79,34 @@ class KRelayFlowTest { job.cancel() } } + +class DispatchPersistedSuspendTest { + + @kotlin.test.BeforeTest + fun setup() { + KRelay.reset() + } + + @Test + fun testDispatchPersistedSuspend_queuesUntilFeatureRegisters() = runTest { + val mock = MockFlowFeature() + KRelay.instance.registerActionFactory("flowFeature", "receive") { payload -> + { feature -> feature.receive(payload) } + } + + // No implementation yet: the action is queued from Dispatchers.Default. + KRelay.instance.dispatchPersistedSuspend("flowFeature", "receive", "hello") + assertEquals(emptyList(), mock.received) + + // Registering replays the queued action. + KRelay.register(mock) + assertEquals(listOf("hello"), mock.received) + } + + @Test + fun testDispatchPersistedSuspend_unknownFactory_throws() = runTest { + kotlin.test.assertFailsWith { + KRelay.instance.dispatchPersistedSuspend("flowFeature", "missing") + } + } +} diff --git a/krelay-testing/build.gradle.kts b/krelay-testing/build.gradle.kts index 20626a9..343869b 100644 --- a/krelay-testing/build.gradle.kts +++ b/krelay-testing/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "dev.brewkits" -version = "2.2.0" +version = "2.3.0" kotlin { androidTarget { diff --git a/krelay/api/android/krelay.api b/krelay/api/android/krelay.api index a24e717..60f3c7c 100644 --- a/krelay/api/android/krelay.api +++ b/krelay/api/android/krelay.api @@ -14,6 +14,11 @@ public final class dev/brewkits/krelay/ActionPriority$Companion { public final fun getDEFAULT ()Ldev/brewkits/krelay/ActionPriority; } +public final class dev/brewkits/krelay/ConsoleLogSink : dev/brewkits/krelay/KRelayLogSink { + public static final field INSTANCE Ldev/brewkits/krelay/ConsoleLogSink; + public fun log (Ldev/brewkits/krelay/KRelayLogLevel;Ljava/lang/String;Ljava/lang/String;)V +} + public final class dev/brewkits/krelay/DebugInfo { public fun (ILjava/util/List;Ljava/util/Map;IIIJZ)V public final fun component1 ()I @@ -152,6 +157,7 @@ public final class dev/brewkits/krelay/KRelayInstanceImpl : dev/brewkits/krelay/ public fun isRegistered (Lkotlin/reflect/KClass;)Z public final fun log (Ljava/lang/String;)V public final fun logError (Ljava/lang/String;)V + public final fun logWarn (Ljava/lang/String;)V public fun register (Lkotlin/reflect/KClass;Ldev/brewkits/krelay/RelayFeature;)V public fun registerActionFactory (Lkotlin/reflect/KClass;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V public fun reset ()V @@ -169,8 +175,60 @@ public final class dev/brewkits/krelay/KRelayKt { public static final fun scopedToken ()Ljava/lang/String; } +public final class dev/brewkits/krelay/KRelayLog { + public static final field INSTANCE Ldev/brewkits/krelay/KRelayLog; + public final fun getMinLevel ()Ldev/brewkits/krelay/KRelayLogLevel; + public final fun getSink ()Ldev/brewkits/krelay/KRelayLogSink; + public final fun reset ()V + public final fun setMinLevel (Ldev/brewkits/krelay/KRelayLogLevel;)V + public final fun setSink (Ldev/brewkits/krelay/KRelayLogSink;)V +} + +public final class dev/brewkits/krelay/KRelayLogLevel : java/lang/Enum { + public static final field DEBUG Ldev/brewkits/krelay/KRelayLogLevel; + public static final field ERROR Ldev/brewkits/krelay/KRelayLogLevel; + public static final field INFO Ldev/brewkits/krelay/KRelayLogLevel; + public static final field WARN Ldev/brewkits/krelay/KRelayLogLevel; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/brewkits/krelay/KRelayLogLevel; + public static fun values ()[Ldev/brewkits/krelay/KRelayLogLevel; +} + +public abstract interface class dev/brewkits/krelay/KRelayLogSink { + public abstract fun log (Ldev/brewkits/krelay/KRelayLogLevel;Ljava/lang/String;Ljava/lang/String;)V +} + +public final class dev/brewkits/krelay/KRelayMetricEvent { + public fun (Ldev/brewkits/krelay/KRelayMetricType;Ljava/lang/String;Ljava/lang/String;I)V + public final fun component1 ()Ldev/brewkits/krelay/KRelayMetricType; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()I + public final fun copy (Ldev/brewkits/krelay/KRelayMetricType;Ljava/lang/String;Ljava/lang/String;I)Ldev/brewkits/krelay/KRelayMetricEvent; + public static synthetic fun copy$default (Ldev/brewkits/krelay/KRelayMetricEvent;Ldev/brewkits/krelay/KRelayMetricType;Ljava/lang/String;Ljava/lang/String;IILjava/lang/Object;)Ldev/brewkits/krelay/KRelayMetricEvent; + public fun equals (Ljava/lang/Object;)Z + public final fun getCount ()I + public final fun getFeatureName ()Ljava/lang/String; + public final fun getScopeName ()Ljava/lang/String; + public final fun getType ()Ldev/brewkits/krelay/KRelayMetricType; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/brewkits/krelay/KRelayMetricType : java/lang/Enum { + public static final field CLEAR Ldev/brewkits/krelay/KRelayMetricType; + public static final field DISPATCH Ldev/brewkits/krelay/KRelayMetricType; + public static final field EXPIRY Ldev/brewkits/krelay/KRelayMetricType; + public static final field QUEUE Ldev/brewkits/krelay/KRelayMetricType; + public static final field REPLAY Ldev/brewkits/krelay/KRelayMetricType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/brewkits/krelay/KRelayMetricType; + public static fun values ()[Ldev/brewkits/krelay/KRelayMetricType; +} + public final class dev/brewkits/krelay/KRelayMetrics { public static final field INSTANCE Ldev/brewkits/krelay/KRelayMetrics; + public final fun addReporter (Ldev/brewkits/krelay/KRelayMetricsReporter;)V public final fun getAllMetrics ()Ljava/util/Map; public final fun getClearCount (Lkotlin/reflect/KClass;)J public final fun getDispatchCount (Lkotlin/reflect/KClass;)J @@ -180,11 +238,16 @@ public final class dev/brewkits/krelay/KRelayMetrics { public final fun getQueueCount (Lkotlin/reflect/KClass;)J public final fun getReplayCount (Lkotlin/reflect/KClass;)J public final fun printReport ()V + public final fun removeReporter (Ldev/brewkits/krelay/KRelayMetricsReporter;)V public final fun reset ()V public final fun resetInstance (Ljava/lang/String;)V public final fun setEnabled (Z)V } +public abstract interface class dev/brewkits/krelay/KRelayMetricsReporter { + public abstract fun onMetric (Ldev/brewkits/krelay/KRelayMetricEvent;)V +} + public abstract interface class dev/brewkits/krelay/KRelayPersistenceAdapter { public abstract fun clearAll ()V public abstract fun clearScope (Ljava/lang/String;)V diff --git a/krelay/api/jvm/krelay.api b/krelay/api/jvm/krelay.api index 9b1df72..8c4b579 100644 --- a/krelay/api/jvm/krelay.api +++ b/krelay/api/jvm/krelay.api @@ -14,6 +14,11 @@ public final class dev/brewkits/krelay/ActionPriority$Companion { public final fun getDEFAULT ()Ldev/brewkits/krelay/ActionPriority; } +public final class dev/brewkits/krelay/ConsoleLogSink : dev/brewkits/krelay/KRelayLogSink { + public static final field INSTANCE Ldev/brewkits/krelay/ConsoleLogSink; + public fun log (Ldev/brewkits/krelay/KRelayLogLevel;Ljava/lang/String;Ljava/lang/String;)V +} + public final class dev/brewkits/krelay/DebugInfo { public fun (ILjava/util/List;Ljava/util/Map;IIIJZ)V public final fun component1 ()I @@ -152,6 +157,7 @@ public final class dev/brewkits/krelay/KRelayInstanceImpl : dev/brewkits/krelay/ public fun isRegistered (Lkotlin/reflect/KClass;)Z public final fun log (Ljava/lang/String;)V public final fun logError (Ljava/lang/String;)V + public final fun logWarn (Ljava/lang/String;)V public fun register (Lkotlin/reflect/KClass;Ldev/brewkits/krelay/RelayFeature;)V public fun registerActionFactory (Lkotlin/reflect/KClass;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V public fun reset ()V @@ -169,8 +175,60 @@ public final class dev/brewkits/krelay/KRelayKt { public static final fun scopedToken ()Ljava/lang/String; } +public final class dev/brewkits/krelay/KRelayLog { + public static final field INSTANCE Ldev/brewkits/krelay/KRelayLog; + public final fun getMinLevel ()Ldev/brewkits/krelay/KRelayLogLevel; + public final fun getSink ()Ldev/brewkits/krelay/KRelayLogSink; + public final fun reset ()V + public final fun setMinLevel (Ldev/brewkits/krelay/KRelayLogLevel;)V + public final fun setSink (Ldev/brewkits/krelay/KRelayLogSink;)V +} + +public final class dev/brewkits/krelay/KRelayLogLevel : java/lang/Enum { + public static final field DEBUG Ldev/brewkits/krelay/KRelayLogLevel; + public static final field ERROR Ldev/brewkits/krelay/KRelayLogLevel; + public static final field INFO Ldev/brewkits/krelay/KRelayLogLevel; + public static final field WARN Ldev/brewkits/krelay/KRelayLogLevel; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/brewkits/krelay/KRelayLogLevel; + public static fun values ()[Ldev/brewkits/krelay/KRelayLogLevel; +} + +public abstract interface class dev/brewkits/krelay/KRelayLogSink { + public abstract fun log (Ldev/brewkits/krelay/KRelayLogLevel;Ljava/lang/String;Ljava/lang/String;)V +} + +public final class dev/brewkits/krelay/KRelayMetricEvent { + public fun (Ldev/brewkits/krelay/KRelayMetricType;Ljava/lang/String;Ljava/lang/String;I)V + public final fun component1 ()Ldev/brewkits/krelay/KRelayMetricType; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()I + public final fun copy (Ldev/brewkits/krelay/KRelayMetricType;Ljava/lang/String;Ljava/lang/String;I)Ldev/brewkits/krelay/KRelayMetricEvent; + public static synthetic fun copy$default (Ldev/brewkits/krelay/KRelayMetricEvent;Ldev/brewkits/krelay/KRelayMetricType;Ljava/lang/String;Ljava/lang/String;IILjava/lang/Object;)Ldev/brewkits/krelay/KRelayMetricEvent; + public fun equals (Ljava/lang/Object;)Z + public final fun getCount ()I + public final fun getFeatureName ()Ljava/lang/String; + public final fun getScopeName ()Ljava/lang/String; + public final fun getType ()Ldev/brewkits/krelay/KRelayMetricType; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/brewkits/krelay/KRelayMetricType : java/lang/Enum { + public static final field CLEAR Ldev/brewkits/krelay/KRelayMetricType; + public static final field DISPATCH Ldev/brewkits/krelay/KRelayMetricType; + public static final field EXPIRY Ldev/brewkits/krelay/KRelayMetricType; + public static final field QUEUE Ldev/brewkits/krelay/KRelayMetricType; + public static final field REPLAY Ldev/brewkits/krelay/KRelayMetricType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/brewkits/krelay/KRelayMetricType; + public static fun values ()[Ldev/brewkits/krelay/KRelayMetricType; +} + public final class dev/brewkits/krelay/KRelayMetrics { public static final field INSTANCE Ldev/brewkits/krelay/KRelayMetrics; + public final fun addReporter (Ldev/brewkits/krelay/KRelayMetricsReporter;)V public final fun getAllMetrics ()Ljava/util/Map; public final fun getClearCount (Lkotlin/reflect/KClass;)J public final fun getDispatchCount (Lkotlin/reflect/KClass;)J @@ -180,11 +238,16 @@ public final class dev/brewkits/krelay/KRelayMetrics { public final fun getQueueCount (Lkotlin/reflect/KClass;)J public final fun getReplayCount (Lkotlin/reflect/KClass;)J public final fun printReport ()V + public final fun removeReporter (Ldev/brewkits/krelay/KRelayMetricsReporter;)V public final fun reset ()V public final fun resetInstance (Ljava/lang/String;)V public final fun setEnabled (Z)V } +public abstract interface class dev/brewkits/krelay/KRelayMetricsReporter { + public abstract fun onMetric (Ldev/brewkits/krelay/KRelayMetricEvent;)V +} + public abstract interface class dev/brewkits/krelay/KRelayPersistenceAdapter { public abstract fun clearAll ()V public abstract fun clearScope (Ljava/lang/String;)V diff --git a/krelay/build.gradle.kts b/krelay/build.gradle.kts index 4eceff5..0cee768 100644 --- a/krelay/build.gradle.kts +++ b/krelay/build.gradle.kts @@ -12,7 +12,7 @@ plugins { } group = "dev.brewkits" -version = "2.2.0" +version = "2.3.0" kotlin { androidTarget { diff --git a/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayInstanceImpl.kt b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayInstanceImpl.kt index fd24838..cfaf8b4 100644 --- a/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayInstanceImpl.kt +++ b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayInstanceImpl.kt @@ -71,7 +71,7 @@ internal class KRelayInstanceImpl( // Same-class replacement (e.g. Activity recreated by Compose lifecycle) is // expected and not a developer mistake — suppress to avoid log noise. if (existing::class != impl::class) { - log("[WARN] Overwriting ${kClass.simpleName}: replacing ${existing::class.simpleName} with ${impl::class.simpleName}. " + + logWarn("Overwriting ${kClass.simpleName}: replacing ${existing::class.simpleName} with ${impl::class.simpleName}. " + "If unintentional, check that only one component registers this feature at a time.") } } @@ -85,8 +85,9 @@ internal class KRelayInstanceImpl( val validActions = queue.filter { !it.isExpired(actionExpiryMs) } val expiredCount = queue.size - validActions.size - if (expiredCount > 0 && debugMode) { - log("[EXPIRY] Removed $expiredCount expired action(s) for ${kClass.simpleName}") + if (expiredCount > 0) { + if (debugMode) log("[EXPIRY] Removed $expiredCount expired action(s) for ${kClass.simpleName}") + KRelayMetrics.recordExpiry(kClass, expiredCount, scopeName) } queue.clear() @@ -95,7 +96,7 @@ internal class KRelayInstanceImpl( if (debugMode) { log("[REPLAY] Replaying ${validActions.size} pending action(s) for ${kClass.simpleName}") } - KRelayMetrics.recordReplay(kClass, validActions.size) + KRelayMetrics.recordReplay(kClass, validActions.size, scopeName) } validActions.toList() @@ -141,7 +142,7 @@ internal class KRelayInstanceImpl( } if (impl != null) { - KRelayMetrics.recordDispatch(kClass) + KRelayMetrics.recordDispatch(kClass, scopeName) runOnMain { try { block(impl) @@ -150,7 +151,7 @@ internal class KRelayInstanceImpl( } } } else { - KRelayMetrics.recordQueue(kClass) + KRelayMetrics.recordQueue(kClass, scopeName) } } @@ -180,7 +181,7 @@ internal class KRelayInstanceImpl( } if (impl != null) { - KRelayMetrics.recordDispatch(kClass) + KRelayMetrics.recordDispatch(kClass, scopeName) runOnMain { try { block(impl) @@ -189,7 +190,7 @@ internal class KRelayInstanceImpl( } } } else { - KRelayMetrics.recordQueue(kClass) + KRelayMetrics.recordQueue(kClass, scopeName) } } @@ -214,7 +215,7 @@ internal class KRelayInstanceImpl( } if (debugMode) { - log("[WARN] Queue full for ${kClass.simpleName}. Evicted ${if (evictByPriority) "lowest-priority" else "oldest"} action.") + logWarn("Queue full for ${kClass.simpleName}. Evicted ${if (evictByPriority) "lowest-priority" else "oldest"} action.") } } @@ -278,7 +279,7 @@ internal class KRelayInstanceImpl( if (debugMode) { log("[CLEAR] Cleared queue for ${kClass.simpleName} ($count actions removed)") } - KRelayMetrics.recordClear(kClass, count) + KRelayMetrics.recordClear(kClass, count, scopeName) } } @@ -490,7 +491,7 @@ internal class KRelayInstanceImpl( } if (impl != null) { - KRelayMetrics.recordDispatch(kClass) + KRelayMetrics.recordDispatch(kClass, scopeName) runOnMain { try { block(impl) @@ -504,7 +505,7 @@ internal class KRelayInstanceImpl( _persistenceAdapter.save(scopeName, featureKey, command) if (debugMode) log("[PERSIST] Persisted $featureKey::$actionKey to storage") } - KRelayMetrics.recordQueue(kClass) + KRelayMetrics.recordQueue(kClass, scopeName) } } @@ -552,7 +553,7 @@ internal class KRelayInstanceImpl( persistedMap.forEach featureLoop@{ (featureKey, commands) -> val kClass = featureKeyToKClass[featureKey] if (kClass == null) { - if (debugMode) log("[WARN] No KClass for '$featureKey'. Register factory before restorePersistedActions().") + if (debugMode) logWarn("No KClass for '$featureKey'. Register factory before restorePersistedActions().") skippedNoFactory += commands.size commands.forEach { toRemove.add(EnqueuedEntry(featureKey, it)) } return@featureLoop @@ -569,7 +570,7 @@ internal class KRelayInstanceImpl( @Suppress("UNCHECKED_CAST") val factory = actionFactories[factoryKey] as? ActionFactory if (factory == null) { - if (debugMode) log("[WARN] No factory for '$factoryKey'. Skipping restored action.") + if (debugMode) logWarn("No factory for '$factoryKey'. Skipping restored action.") skippedNoFactory++ toRemove.add(EnqueuedEntry(featureKey, command)) return@commandLoop @@ -606,7 +607,16 @@ internal class KRelayInstanceImpl( */ @PublishedApi internal fun log(message: String) { - println("[KRelay][$scopeName] $message") + KRelayLog.emit(KRelayLogLevel.DEBUG, scopeName, message) + } + + /** + * Internal logging function for warnings. Call sites decide whether to guard with + * `if (debugMode)`; the record is delivered to [KRelayLog.sink] at WARN level. + */ + @PublishedApi + internal fun logWarn(message: String) { + KRelayLog.emit(KRelayLogLevel.WARN, scopeName, message) } /** @@ -615,6 +625,6 @@ internal class KRelayInstanceImpl( */ @PublishedApi internal fun logError(message: String) { - println("[KRelay][$scopeName][ERROR] $message") + KRelayLog.emit(KRelayLogLevel.ERROR, scopeName, message) } } diff --git a/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayLog.kt b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayLog.kt new file mode 100644 index 0000000..ef9f345 --- /dev/null +++ b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayLog.kt @@ -0,0 +1,67 @@ +package dev.brewkits.krelay + +/** + * Severity of a KRelay log record, ordered from least to most severe. + */ +enum class KRelayLogLevel { DEBUG, INFO, WARN, ERROR } + +/** + * Destination for KRelay log records (Logcat, os_log, Timber, Datadog, a file, ...). + * + * Implementations may be called from any thread and must not throw; exceptions are swallowed + * so a faulty sink can never break a dispatch. + */ +fun interface KRelayLogSink { + fun log(level: KRelayLogLevel, scopeName: String, message: String) +} + +/** + * Global configuration for KRelay's structured logging. + * + * By default records go to the console. DEBUG and most WARN records are only produced when the + * owning instance has `debugMode` enabled; ERROR records (e.g. an action that threw) are + * always produced. + * + * ```kotlin + * KRelayLog.minLevel = KRelayLogLevel.WARN + * KRelayLog.sink = KRelayLogSink { level, scope, message -> + * MyLogger.log(level.name, "KRelay[$scope]", message) + * } + * ``` + */ +object KRelayLog { + /** Records below this level are dropped before reaching [sink]. Default: [KRelayLogLevel.DEBUG]. */ + var minLevel: KRelayLogLevel = KRelayLogLevel.DEBUG + + /** Where records are delivered. Default: [ConsoleLogSink]. */ + var sink: KRelayLogSink = ConsoleLogSink + + /** Restores the default console sink and minimum level. */ + fun reset() { + minLevel = KRelayLogLevel.DEBUG + sink = ConsoleLogSink + } + + internal fun emit(level: KRelayLogLevel, scopeName: String, message: String) { + if (level < minLevel) return + try { + sink.log(level, scopeName, message) + } catch (_: Throwable) { + // A logging sink must never break dispatching. + } + } +} + +/** + * Default [KRelayLogSink]: prints to the console using the pre-2.3.0 format. + */ +object ConsoleLogSink : KRelayLogSink { + override fun log(level: KRelayLogLevel, scopeName: String, message: String) { + val line = when (level) { + KRelayLogLevel.ERROR -> "[KRelay][$scopeName][ERROR] $message" + KRelayLogLevel.WARN -> "[KRelay][$scopeName][WARN] $message" + else -> "[KRelay][$scopeName] $message" + } + println(line) + } +} diff --git a/krelay/src/commonMain/kotlin/dev/brewkits/krelay/Metrics.kt b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/Metrics.kt index 63c934f..542c94a 100644 --- a/krelay/src/commonMain/kotlin/dev/brewkits/krelay/Metrics.kt +++ b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/Metrics.kt @@ -52,6 +52,37 @@ object KRelayMetrics { */ var enabled: Boolean = false + // Copy-on-write so delivery can iterate without holding the lock. + private var reporters: List = emptyList() + + /** + * Adds a [reporter] that receives every recorded metric event (see [KRelayMetricsReporter]). + * Adding the same instance twice has no effect. + */ + fun addReporter(reporter: KRelayMetricsReporter) { + metricsLock.withLock { + if (reporter !in reporters) reporters = reporters + reporter + } + } + + /** Removes a previously added [reporter]. */ + fun removeReporter(reporter: KRelayMetricsReporter) { + metricsLock.withLock { reporters = reporters - reporter } + } + + private fun notifyReporters(type: KRelayMetricType, kClass: KClass<*>, count: Int, scopeName: String) { + val current = metricsLock.withLock { reporters } + if (current.isEmpty()) return + val event = KRelayMetricEvent(type, kClass.simpleName.orEmpty(), scopeName, count) + current.forEach { reporter -> + try { + reporter.onMetric(event) + } catch (_: Throwable) { + // A reporter must never break dispatching. + } + } + } + /** * Records a dispatch event. No-op if [enabled] is false. */ @@ -64,6 +95,7 @@ object KRelayMetrics { scopedDispatchCounts[key] = (scopedDispatchCounts[key] ?: 0) + 1 } } + notifyReporters(KRelayMetricType.DISPATCH, kClass, 1, scopeName) } /** @@ -78,6 +110,7 @@ object KRelayMetrics { scopedQueueCounts[key] = (scopedQueueCounts[key] ?: 0) + 1 } } + notifyReporters(KRelayMetricType.QUEUE, kClass, 1, scopeName) } /** @@ -92,6 +125,7 @@ object KRelayMetrics { scopedReplayCounts[key] = (scopedReplayCounts[key] ?: 0) + count } } + notifyReporters(KRelayMetricType.REPLAY, kClass, count, scopeName) } /** @@ -106,6 +140,7 @@ object KRelayMetrics { scopedExpiryCounts[key] = (scopedExpiryCounts[key] ?: 0) + count } } + notifyReporters(KRelayMetricType.EXPIRY, kClass, count, scopeName) } /** @@ -120,6 +155,7 @@ object KRelayMetrics { scopedClearCounts[key] = (scopedClearCounts[key] ?: 0) + count } } + notifyReporters(KRelayMetricType.CLEAR, kClass, count, scopeName) } /** diff --git a/krelay/src/commonMain/kotlin/dev/brewkits/krelay/MetricsReporter.kt b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/MetricsReporter.kt new file mode 100644 index 0000000..49bcb4e --- /dev/null +++ b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/MetricsReporter.kt @@ -0,0 +1,35 @@ +package dev.brewkits.krelay + +/** + * Kind of event reported to a [KRelayMetricsReporter]. + */ +enum class KRelayMetricType { DISPATCH, QUEUE, REPLAY, EXPIRY, CLEAR } + +/** + * A single metrics event. + * + * @property type What happened. + * @property featureName `simpleName` of the feature interface (may be empty for anonymous types). + * @property scopeName Name of the owning instance, or empty for events recorded without a scope. + * @property count Number of actions the event covers (1 for dispatch/queue, N for replay/expiry/clear). + */ +data class KRelayMetricEvent( + val type: KRelayMetricType, + val featureName: String, + val scopeName: String, + val count: Int +) + +/** + * Receives KRelay metrics events so they can be forwarded to Firebase Performance, Datadog, + * or any custom backend. KRelay ships only this interface — it has no dependency on those SDKs. + * + * Register with [KRelayMetrics.addReporter]. Events are only delivered while + * [KRelayMetrics.enabled] is true. Reporters are invoked synchronously on the thread that + * recorded the event, and for some events (replay, expiry) while KRelay holds its internal + * reentrant lock — keep them fast and non-blocking, and hand heavy work to another thread. + * Exceptions thrown by a reporter are swallowed. + */ +fun interface KRelayMetricsReporter { + fun onMetric(event: KRelayMetricEvent) +} diff --git a/krelay/src/commonTest/kotlin/dev/brewkits/krelay/unit/KRelayLogTest.kt b/krelay/src/commonTest/kotlin/dev/brewkits/krelay/unit/KRelayLogTest.kt new file mode 100644 index 0000000..36dd0c0 --- /dev/null +++ b/krelay/src/commonTest/kotlin/dev/brewkits/krelay/unit/KRelayLogTest.kt @@ -0,0 +1,94 @@ +package dev.brewkits.krelay.unit + +import dev.brewkits.krelay.* +import kotlin.test.* + +/** + * Tests for structured logging: severity filtering, custom sinks, sink isolation. + */ +class KRelayLogTest { + + interface ToastFeature : RelayFeature { + fun show(message: String) + } + + class MockToast : ToastFeature { + override fun show(message: String) {} + } + + private data class Record(val level: KRelayLogLevel, val scope: String, val message: String) + + private val records = mutableListOf() + private lateinit var instance: KRelayInstance + + @BeforeTest + fun setup() { + KRelay.reset() + KRelayLog.reset() + KRelayLog.sink = KRelayLogSink { level, scope, message -> records.add(Record(level, scope, message)) } + instance = KRelay.builder("LogTestScope").debugMode(true).build() + } + + @AfterTest + fun tearDown() { + instance.reset() + KRelay.clearInstanceRegistry() + KRelayLog.reset() + } + + @Test + fun testDebugLogs_reachSink_withScopeName() { + instance.register(MockToast()) + + val debug = records.filter { it.level == KRelayLogLevel.DEBUG } + assertTrue(debug.isNotEmpty()) + assertTrue(debug.all { it.scope == "LogTestScope" }) + } + + @Test + fun testMinLevel_filtersLowerSeverities() { + KRelayLog.minLevel = KRelayLogLevel.WARN + + instance.register(MockToast()) + // Overflowing a queue of size 1 emits a WARN (eviction). + val small = KRelay.builder("SmallQueueScope").debugMode(true).maxQueueSize(1).build() + small.dispatch { it.show("1") } + small.dispatch { it.show("2") } + small.reset() + + assertTrue(records.isNotEmpty()) + assertTrue(records.all { it.level >= KRelayLogLevel.WARN }) + assertTrue(records.any { it.level == KRelayLogLevel.WARN }) + } + + @Test + fun testError_isEmitted_evenWithoutDebugMode() { + val quiet = KRelay.builder("QuietScope").debugMode(false).build() + quiet.register(MockToast()) + quiet.dispatch { error("boom") } + + assertTrue(records.any { it.level == KRelayLogLevel.ERROR && it.scope == "QuietScope" }) + assertTrue(records.none { it.level == KRelayLogLevel.DEBUG && it.scope == "QuietScope" }) + quiet.reset() + } + + @Test + fun testThrowingSink_doesNotBreakDispatch() { + KRelayLog.sink = KRelayLogSink { _, _, _ -> error("sink failure") } + val mock = MockToast() + + instance.register(mock) + instance.dispatch { it.show("still works") } + // reaching here without an exception is the assertion + assertTrue(instance.isRegistered()) + } + + @Test + fun testReset_restoresDefaults() { + KRelayLog.minLevel = KRelayLogLevel.ERROR + KRelayLog.reset() + + assertEquals(KRelayLogLevel.DEBUG, KRelayLog.minLevel) + assertSame(ConsoleLogSink, KRelayLog.sink) + } +} diff --git a/krelay/src/commonTest/kotlin/dev/brewkits/krelay/unit/MetricsReporterTest.kt b/krelay/src/commonTest/kotlin/dev/brewkits/krelay/unit/MetricsReporterTest.kt new file mode 100644 index 0000000..04105e9 --- /dev/null +++ b/krelay/src/commonTest/kotlin/dev/brewkits/krelay/unit/MetricsReporterTest.kt @@ -0,0 +1,118 @@ +package dev.brewkits.krelay.unit + +import dev.brewkits.krelay.* +import kotlin.test.* + +/** + * Tests for [KRelayMetricsReporter]: event delivery, scope attribution, enable flag, isolation. + */ +class MetricsReporterTest { + + interface ToastFeature : RelayFeature { + fun show(message: String) + } + + class MockToast : ToastFeature { + override fun show(message: String) {} + } + + private val events = mutableListOf() + private val reporter = KRelayMetricsReporter { events.add(it) } + private lateinit var instance: KRelayInstance + + @BeforeTest + fun setup() { + KRelayMetrics.reset() + KRelayMetrics.enabled = true + KRelayMetrics.addReporter(reporter) + KRelay.reset() + instance = KRelay.create("ReporterScope") + } + + @AfterTest + fun tearDown() { + KRelayMetrics.removeReporter(reporter) + instance.reset() + KRelay.clearInstanceRegistry() + KRelayMetrics.reset() + KRelayMetrics.enabled = false + } + + @Test + fun testQueueThenReplay_reportsEventsWithScope() { + instance.dispatch { it.show("a") } + instance.dispatch { it.show("b") } + instance.register(MockToast()) + + val queued = events.filter { it.type == KRelayMetricType.QUEUE } + val replayed = events.single { it.type == KRelayMetricType.REPLAY } + assertEquals(2, queued.size) + assertEquals(2, replayed.count) + assertEquals("ToastFeature", replayed.featureName) + assertEquals("ReporterScope", replayed.scopeName) + } + + @Test + fun testImmediateDispatch_reportsDispatchEvent() { + instance.register(MockToast()) + instance.dispatch { it.show("now") } + + val dispatch = events.single { it.type == KRelayMetricType.DISPATCH } + assertEquals(1, dispatch.count) + assertEquals("ReporterScope", dispatch.scopeName) + } + + @Test + fun testInstanceMetrics_areAttributedToScope() { + instance.register(MockToast()) + instance.dispatch { it.show("x") } + + val scoped = KRelayMetrics.getInstanceMetrics("ReporterScope") + assertEquals(1L, scoped["ToastFeature"]?.get("dispatches")) + } + + @Test + fun testClear_reportsClearEvent() { + instance.dispatch { it.show("x") } + instance.clearQueue(ToastFeature::class) + + assertEquals(1, events.single { it.type == KRelayMetricType.CLEAR }.count) + } + + @Test + fun testDisabledMetrics_deliverNoEvents() { + KRelayMetrics.enabled = false + instance.dispatch { it.show("x") } + + assertTrue(events.isEmpty()) + } + + @Test + fun testRemovedReporter_stopsReceiving() { + KRelayMetrics.removeReporter(reporter) + instance.dispatch { it.show("x") } + + assertTrue(events.isEmpty()) + } + + @Test + fun testAddingSameReporterTwice_deliversOnce() { + KRelayMetrics.addReporter(reporter) + instance.dispatch { it.show("x") } + + assertEquals(1, events.count { it.type == KRelayMetricType.QUEUE }) + } + + @Test + fun testThrowingReporter_doesNotBreakDispatch() { + val bad = KRelayMetricsReporter { error("reporter failure") } + KRelayMetrics.addReporter(bad) + try { + instance.dispatch { it.show("x") } + assertEquals(1, KRelayMetrics.getQueueCount(ToastFeature::class)) + assertEquals(1, events.count { it.type == KRelayMetricType.QUEUE }) + } finally { + KRelayMetrics.removeReporter(bad) + } + } +} From bc35565ca1ea38ab3df87b07509c20aa46180b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?= Date: Thu, 1 Oct 2026 00:33:21 +0700 Subject: [PATCH 4/8] =?UTF-8?q?ci:=20audit=20fixes=20=E2=80=94=20remove=20?= =?UTF-8?q?bogus=20publish-snapshot=20job,=20preflight=20publish=20secrets?= =?UTF-8?q?,=20treat=20blank=20signing=20secrets=20as=20unset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - publish-snapshot published the non-SNAPSHOT release version on every push to main and swallowed failures via continue-on-error - release: fail fast with a clear message when maven-central secrets are empty - build: blank SIGNING_KEY/SIGNING_PASSWORD no longer reach useInMemoryPgpKeys - ci: run apiCheck, krelay jvmTest and wasm tests like the release pipeline - KRelayLog: mark sink/minLevel volatile --- .github/workflows/ci.yml | 58 ++----------------- .github/workflows/release.yml | 13 +++++ krelay-bom/build.gradle.kts | 4 +- krelay-compose/build.gradle.kts | 4 +- krelay-flow/build.gradle.kts | 4 +- krelay-testing/build.gradle.kts | 4 +- krelay/build.gradle.kts | 4 +- .../kotlin/dev/brewkits/krelay/KRelayLog.kt | 2 + 8 files changed, 29 insertions(+), 64 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 914a061..c5c3b27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,8 @@ jobs: - uses: gradle/actions/setup-gradle@v6 - name: Run Android Lint (krelay) run: ./gradlew :krelay:lintDebug --no-daemon + - name: Check public API is unchanged + run: ./gradlew :krelay:apiCheck :krelay-compose:apiCheck --no-daemon # ────────────────────────────────────────────────────────────────────────── test-android: @@ -37,6 +39,8 @@ jobs: run: ./gradlew :krelay:testDebugUnitTest --no-daemon - name: Run krelay-flow unit tests run: ./gradlew :krelay-flow:testDebugUnitTest :krelay-flow:jvmTest --no-daemon + - name: Run JVM and Wasm tests + run: ./gradlew :krelay:jvmTest :krelay:wasmJsBrowserTest :krelay-flow:wasmJsBrowserTest --no-daemon - name: Run krelay-testing unit tests run: ./gradlew :krelay-testing:testDebugUnitTest --no-daemon - name: Upload test results @@ -102,57 +106,3 @@ jobs: with: name: krelay-artifacts path: krelay/build/outputs/ - - # ────────────────────────────────────────────────────────────────────────── - publish-snapshot: - name: Publish Snapshot - runs-on: macos-14 - needs: [ build ] - if: github.ref == 'refs/heads/main' && github.event_name == 'push' - steps: - - uses: actions/checkout@v7 - - uses: actions/setup-java@v6 - with: - java-version: '17' - distribution: 'temurin' - - uses: gradle/actions/setup-gradle@v6 - - name: Publish krelay to OSSRH - run: ./gradlew :krelay:publishAllPublicationsToOSSRHRepository --no-daemon - env: - OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} - OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} - continue-on-error: true - - name: Publish krelay-flow to OSSRH - run: ./gradlew :krelay-flow:publishAllPublicationsToOSSRHRepository --no-daemon - env: - OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} - OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} - continue-on-error: true - - name: Publish krelay-compose to OSSRH - run: ./gradlew :krelay-compose:publishAllPublicationsToOSSRHRepository --no-daemon - env: - OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} - OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} - continue-on-error: true - - name: Publish krelay-testing to OSSRH - run: ./gradlew :krelay-testing:publishAllPublicationsToOSSRHRepository --no-daemon - env: - OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} - OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} - continue-on-error: true - - name: Publish krelay-bom to OSSRH - run: ./gradlew :krelay-bom:publishAllPublicationsToOSSRHRepository --no-daemon - env: - OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} - OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} - continue-on-error: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1bde5f7..d1b10d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -143,6 +143,19 @@ jobs: distribution: 'temurin' - uses: gradle/actions/setup-gradle@v6 + - name: Verify publishing secrets are set + run: | + missing=0 + for v in OSSRH_USERNAME OSSRH_PASSWORD SIGNING_KEY SIGNING_PASSWORD; do + if [ -z "${!v}" ]; then echo "::error::Secret $v is empty or not set in the maven-central environment"; missing=1; fi + done + exit $missing + env: + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} + - name: Publish krelay run: ./gradlew :krelay:publishAllPublicationsToOSSRHRepository --no-daemon env: diff --git a/krelay-bom/build.gradle.kts b/krelay-bom/build.gradle.kts index 32eb0b2..8d8d7f6 100644 --- a/krelay-bom/build.gradle.kts +++ b/krelay-bom/build.gradle.kts @@ -74,8 +74,8 @@ publishing { } signing { - val rawKey = findProperty("signing.key")?.toString() ?: System.getenv("SIGNING_KEY") - val signingPassword = findProperty("signing.password")?.toString() ?: System.getenv("SIGNING_PASSWORD") + val rawKey = (findProperty("signing.key")?.toString() ?: System.getenv("SIGNING_KEY"))?.takeIf { it.isNotBlank() } + val signingPassword = (findProperty("signing.password")?.toString() ?: System.getenv("SIGNING_PASSWORD"))?.takeIf { it.isNotBlank() } if (rawKey != null && signingPassword != null) { val decoded = try { val d = String(Base64.getDecoder().decode(rawKey)) diff --git a/krelay-compose/build.gradle.kts b/krelay-compose/build.gradle.kts index c6ad50e..351e140 100644 --- a/krelay-compose/build.gradle.kts +++ b/krelay-compose/build.gradle.kts @@ -129,8 +129,8 @@ publishing { } signing { - val rawKey = findProperty("signing.key")?.toString() ?: System.getenv("SIGNING_KEY") - val signingPassword = findProperty("signing.password")?.toString() ?: System.getenv("SIGNING_PASSWORD") + val rawKey = (findProperty("signing.key")?.toString() ?: System.getenv("SIGNING_KEY"))?.takeIf { it.isNotBlank() } + val signingPassword = (findProperty("signing.password")?.toString() ?: System.getenv("SIGNING_PASSWORD"))?.takeIf { it.isNotBlank() } if (rawKey != null && signingPassword != null) { val signingKey = try { diff --git a/krelay-flow/build.gradle.kts b/krelay-flow/build.gradle.kts index 842ebe0..c665238 100644 --- a/krelay-flow/build.gradle.kts +++ b/krelay-flow/build.gradle.kts @@ -129,8 +129,8 @@ publishing { } signing { - val rawKey = findProperty("signing.key")?.toString() ?: System.getenv("SIGNING_KEY") - val signingPassword = findProperty("signing.password")?.toString() ?: System.getenv("SIGNING_PASSWORD") + val rawKey = (findProperty("signing.key")?.toString() ?: System.getenv("SIGNING_KEY"))?.takeIf { it.isNotBlank() } + val signingPassword = (findProperty("signing.password")?.toString() ?: System.getenv("SIGNING_PASSWORD"))?.takeIf { it.isNotBlank() } if (rawKey != null && signingPassword != null) { val signingKey = try { diff --git a/krelay-testing/build.gradle.kts b/krelay-testing/build.gradle.kts index 343869b..ce7c6da 100644 --- a/krelay-testing/build.gradle.kts +++ b/krelay-testing/build.gradle.kts @@ -113,8 +113,8 @@ publishing { } signing { - val rawKey = findProperty("signing.key")?.toString() ?: System.getenv("SIGNING_KEY") - val signingPassword = findProperty("signing.password")?.toString() ?: System.getenv("SIGNING_PASSWORD") + val rawKey = (findProperty("signing.key")?.toString() ?: System.getenv("SIGNING_KEY"))?.takeIf { it.isNotBlank() } + val signingPassword = (findProperty("signing.password")?.toString() ?: System.getenv("SIGNING_PASSWORD"))?.takeIf { it.isNotBlank() } if (rawKey != null && signingPassword != null) { val signingKey = try { val decoded = String(Base64.getDecoder().decode(rawKey)) diff --git a/krelay/build.gradle.kts b/krelay/build.gradle.kts index 0cee768..dc127a8 100644 --- a/krelay/build.gradle.kts +++ b/krelay/build.gradle.kts @@ -180,8 +180,8 @@ publishing { } signing { - val rawKey = findProperty("signing.key")?.toString() ?: System.getenv("SIGNING_KEY") - val signingPassword = findProperty("signing.password")?.toString() ?: System.getenv("SIGNING_PASSWORD") + val rawKey = (findProperty("signing.key")?.toString() ?: System.getenv("SIGNING_KEY"))?.takeIf { it.isNotBlank() } + val signingPassword = (findProperty("signing.password")?.toString() ?: System.getenv("SIGNING_PASSWORD"))?.takeIf { it.isNotBlank() } if (rawKey != null && signingPassword != null) { // Key may be stored as base64 in gradle.properties — decode if needed diff --git a/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayLog.kt b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayLog.kt index ef9f345..57c3337 100644 --- a/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayLog.kt +++ b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayLog.kt @@ -31,9 +31,11 @@ fun interface KRelayLogSink { */ object KRelayLog { /** Records below this level are dropped before reaching [sink]. Default: [KRelayLogLevel.DEBUG]. */ + @kotlin.concurrent.Volatile var minLevel: KRelayLogLevel = KRelayLogLevel.DEBUG /** Where records are delivered. Default: [ConsoleLogSink]. */ + @kotlin.concurrent.Volatile var sink: KRelayLogSink = ConsoleLogSink /** Restores the default console sink and minimum level. */ From 3a17e098dbd7f6be6229f28ab636174dcd90c6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?= Date: Thu, 1 Oct 2026 00:59:59 +0700 Subject: [PATCH 5/8] docs: v2.3.0 migration guide, README What's New and compatibility table --- README.md | 20 ++++++++++++++ docs/MIGRATION_V2_3.md | 61 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 docs/MIGRATION_V2_3.md diff --git a/README.md b/README.md index ed74e32..c36a430 100644 --- a/README.md +++ b/README.md @@ -399,6 +399,7 @@ See [Integration Guides](docs/INTEGRATION_GUIDES.md) for step-by-step examples. | KRelay | Kotlin | AGP | Android minSdk | iOS | Desktop (JVM) | WasmJs | |---|---|---|---|---|---|---| +| 2.3.x | 2.2+ (built with 2.3.21) | 8.x | 24 | 14.0+ | āœ… | āœ… | | 2.2.x | 2.1.x | 8.x | 24 | 14.0+ | āœ… | āœ… | | 2.1.x | 2.1.x | 8.x | 24 | 14.0+ | — | — | | 2.0.x | 2.1.x | 8.x | 24 | 14.0+ | — | — | @@ -412,6 +413,23 @@ See [Integration Guides](docs/INTEGRATION_GUIDES.md) for step-by-step examples. ## What's New
+v2.3.0 — Observability (Oct 2026) + +- **Structured logging** — `KRelayLog` with severity levels and pluggable `KRelayLogSink`. +- **Metrics reporter** — `KRelayMetricsReporter` forwards dispatch / queue / replay / expiry / clear events to Firebase, Datadog or your own backend. See [Monitoring & Logging](docs/MONITORING.md). +- **`dispatchPersistedSuspend`** in `krelay-flow` — persist from coroutines without blocking on storage I/O. +- **Fixes** — per-instance metrics are now actually attributed to their scope; expired actions are counted. +- **Toolchain** — built with Kotlin 2.3.21 and Compose Multiplatform 1.10.3. Upgrade guide: [Migration to v2.3](docs/MIGRATION_V2_3.md). +
+ +
+v2.2.0 — Desktop, Web & Flow Adapter (Sep 2026) + +- JVM Desktop and Kotlin/WasmJs targets +- `krelay-flow` artifact with the `relayTo` operator +
+ +
v2.1.1 — QA Hardening & Ecosystem Infrastructure (Sep 2026) - **`krelay-testing` artifact** — `FakeKRelayInstance` with a full assertion API (`assertDispatched`, `assertNotDispatched`, `executeLastDispatch`) for clean, mock-free unit testing. @@ -457,6 +475,8 @@ See [Integration Guides](docs/INTEGRATION_GUIDES.md) for step-by-step examples. | [Architecture](docs/ARCHITECTURE.md) | Internals deep dive | | [API Reference](docs/QUICK_REFERENCE.md) | Full API cheat sheet | | [Managing Warnings](docs/MANAGING_WARNINGS.md) | Suppress `@OptIn` at module level | +| [Monitoring & Logging](docs/MONITORING.md) | `KRelayLog`, `KRelayMetricsReporter` | +| [Migration to v2.3](docs/MIGRATION_V2_3.md) | Toolchain requirements and behaviour changes | | [Migration to v2.0](docs/MIGRATION_V2.md) | Upgrading from v1.x | --- diff --git a/docs/MIGRATION_V2_3.md b/docs/MIGRATION_V2_3.md new file mode 100644 index 0000000..4793a12 --- /dev/null +++ b/docs/MIGRATION_V2_3.md @@ -0,0 +1,61 @@ +# Migrating to KRelay 2.3.0 + +2.3.0 is source- and binary-compatible with 2.2.x: no public API was removed or changed +(`apiCheck` only shows additions). What changes for you is the **toolchain KRelay is built with** +and a few behaviours. + +## 1. Toolchain requirements + +KRelay 2.3.0 is compiled with **Kotlin 2.3.21** and (for `krelay-compose`) **Compose Multiplatform 1.10.3**. + +- Kotlin compilers can read libraries built by the next minor version at most, so use **Kotlin 2.2 or newer** + in your project. Kotlin 2.1.x is not supported by 2.3.0; stay on KRelay 2.2.x if you cannot upgrade yet. +- If you use `krelay-compose`, align your Compose Multiplatform version with 1.10.x + (the BOM does not manage Compose). +- Android Gradle Plugin and minSdk (24) requirements are unchanged. + +```kotlin +commonMain.dependencies { + api(platform("dev.brewkits:krelay-bom:2.3.0")) + implementation("dev.brewkits:krelay") +} +``` + +## 2. Behaviour changes + +| Area | Before (2.2.x) | Now (2.3.0) | +|---|---|---| +| `KRelayMetrics.getInstanceMetrics(scope)` | Always empty — events were recorded without the instance scope | Populated per instance | +| Expired actions dropped on replay | Not counted | Counted via `recordExpiry` (`expired` in metrics) | +| Warning log format | `[KRelay][scope] [WARN] …` | `[KRelay][scope][WARN] …` | + +If you parse KRelay's console output or assert on metrics in your own tests, adjust accordingly. + +## 3. New opt-in APIs + +### Structured logging +```kotlin +KRelayLog.minLevel = KRelayLogLevel.WARN +KRelayLog.sink = KRelayLogSink { level, scope, message -> MyLogger.log(level.name, scope, message) } +``` + +### Metrics reporter +```kotlin +KRelayMetrics.enabled = true +KRelayMetrics.addReporter { event -> MyAnalytics.count("krelay.${event.type}", event.count) } +``` + +### `dispatchPersistedSuspend` (`krelay-flow`) +```kotlin +viewModelScope.launch { + relay.dispatchPersistedSuspend("toast", "show", payload = "Saved") +} +``` + +See [Monitoring & Logging](MONITORING.md) for details. Nothing needs to change if you do not use these. + +## 4. Publishing / CI (maintainers only) + +Releases are published through the Central Portal OSSRH staging API; the legacy +`s01.oss.sonatype.org` endpoint no longer exists. The `maven-central` GitHub environment needs +`OSSRH_USERNAME` / `OSSRH_PASSWORD` (Portal **user token**), `SIGNING_KEY` and `SIGNING_PASSWORD`. From c2323e0c6b2e62627bd7095c8918c497c3633556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?= Date: Thu, 1 Oct 2026 01:33:01 +0700 Subject: [PATCH 6/8] fix(demo): keep strong refs to registered features, persist selected demo on rotation, migrate to modular moko-permissions - MainActivity/BasicDemo/RealIntegrations registered inline instances held only by WeakRef, so they were GC'd and dispatches were queued ('Implementation missing') - App: selected demo survives rotation (rememberSaveable) - moko-permissions 0.18.0 -> 0.20.1 (modular API): 0.18.0 klib crashed the Kotlin/Native 2.3 linker ('IrSimpleFunctionSymbolImpl is already bound'), which broke the iOS framework link in CI - remove dead 'if (false)' block in IntegrationsDemo --- composeApp/build.gradle.kts | 4 ++ .../dev/brewkits/krelay/MainActivity.kt | 19 +++++--- .../integrations/AndroidIntegrations.kt | 4 ++ .../kotlin/dev/brewkits/krelay/App.kt | 17 ++++--- .../dev/brewkits/krelay/basic/BasicDemo.kt | 10 +++-- .../krelay/integrations/IntegrationsDemo.kt | 44 ------------------- .../krelay/integrations/RealIntegrations.kt | 4 +- .../krelay/integrations/IOSIntegrations.kt | 4 ++ gradle/libs.versions.toml | 6 ++- 9 files changed, 52 insertions(+), 60 deletions(-) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 60dd197..8f51bd6 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -65,6 +65,10 @@ kotlin { // Moko libraries for KMP implementation(libs.moko.permissions) implementation(libs.moko.permissions.compose) + implementation(libs.moko.permissions.camera) + implementation(libs.moko.permissions.location) + implementation(libs.moko.permissions.microphone) + implementation(libs.moko.permissions.gallery) implementation(libs.moko.biometry) implementation(libs.moko.biometry.compose) diff --git a/composeApp/src/androidMain/kotlin/dev/brewkits/krelay/MainActivity.kt b/composeApp/src/androidMain/kotlin/dev/brewkits/krelay/MainActivity.kt index e55a9f0..77f201f 100644 --- a/composeApp/src/androidMain/kotlin/dev/brewkits/krelay/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/dev/brewkits/krelay/MainActivity.kt @@ -19,6 +19,15 @@ import dev.brewkits.krelay.superapp.foodKRelay import dev.brewkits.krelay.superapp.ridesKRelay class MainActivity : FragmentActivity() { + + // KRelay holds implementations via WeakReference: keep strong references for the lifetime + // of the Activity, otherwise they can be garbage-collected and dispatches will be queued. + private val toastFeature by lazy { AndroidToastFeature(applicationContext) } + private val notificationBridge by lazy { AndroidNotificationBridge(applicationContext) } + private val navigationFeature by lazy { AndroidNavigationFeature(applicationContext) } + private val ridesToastFeature by lazy { AndroidToastFeature(applicationContext) } + private val foodToastFeature by lazy { AndroidToastFeature(applicationContext) } + override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) @@ -27,20 +36,20 @@ class MainActivity : FragmentActivity() { KRelay.debugMode = true // Register platform implementations for the default singleton - KRelay.register(AndroidToastFeature(applicationContext)) - KRelay.register(AndroidNotificationBridge(applicationContext)) - KRelay.register(AndroidNavigationFeature(applicationContext)) + KRelay.register(toastFeature) + KRelay.register(notificationBridge) + KRelay.register(navigationFeature) // Register implementations for the Super App demo instances // IMPORTANT: Each instance needs its own platform implementation Log.d("MainActivity", "Registering Super App instances...") ridesKRelay.debugMode = true - ridesKRelay.register(AndroidToastFeature(applicationContext)) + ridesKRelay.register(toastFeature) Log.d("MainActivity", "Rides KRelay registered: ${ridesKRelay.isRegistered()}") foodKRelay.debugMode = true - foodKRelay.register(AndroidToastFeature(applicationContext)) + foodKRelay.register(toastFeature) Log.d("MainActivity", "Food KRelay registered: ${foodKRelay.isRegistered()}") setContent { diff --git a/composeApp/src/androidMain/kotlin/dev/brewkits/krelay/integrations/AndroidIntegrations.kt b/composeApp/src/androidMain/kotlin/dev/brewkits/krelay/integrations/AndroidIntegrations.kt index 6d3da31..957f78c 100644 --- a/composeApp/src/androidMain/kotlin/dev/brewkits/krelay/integrations/AndroidIntegrations.kt +++ b/composeApp/src/androidMain/kotlin/dev/brewkits/krelay/integrations/AndroidIntegrations.kt @@ -13,6 +13,10 @@ import dev.icerock.moko.permissions.Permission import dev.icerock.moko.permissions.PermissionsController import dev.icerock.moko.permissions.compose.BindEffect import dev.icerock.moko.permissions.compose.rememberPermissionsControllerFactory +import dev.icerock.moko.permissions.camera.CAMERA +import dev.icerock.moko.permissions.gallery.GALLERY +import dev.icerock.moko.permissions.location.LOCATION +import dev.icerock.moko.permissions.microphone.RECORD_AUDIO import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/App.kt b/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/App.kt index 828cfca..8f1aa65 100644 --- a/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/App.kt +++ b/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/App.kt @@ -5,6 +5,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight @@ -18,15 +20,12 @@ import dev.brewkits.krelay.integrations.IntegrationsDemo import dev.brewkits.krelay.superapp.SuperAppDemo /** - * Main App entry point. - * - * Now defaults to IntegrationsDemo with REAL implementations. - * Menu available to switch between demos if needed. + * Main App entry point: a menu that opens one of the KRelay demos. + * The selected demo survives configuration changes such as screen rotation. */ @Composable fun App() { - // Default to IntegrationsDemo (REAL implementations only) - var selectedDemo by remember { mutableStateOf(null) } + var selectedDemo by rememberSaveable(stateSaver = DemoTypeSaver) { mutableStateOf(null) } when (selectedDemo) { DemoType.BASIC -> BasicDemo(onBackClick = { selectedDemo = null }) @@ -199,3 +198,9 @@ enum class DemoType { INTEGRATIONS, SUPER_APP } + +/** Stores the selected demo by name so it can be restored after rotation or process recreation. */ +private val DemoTypeSaver: Saver = Saver( + save = { it?.name ?: "" }, + restore = { name -> DemoType.entries.firstOrNull { it.name == name } } +) diff --git a/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/basic/BasicDemo.kt b/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/basic/BasicDemo.kt index 5610a8c..6bd3ba2 100644 --- a/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/basic/BasicDemo.kt +++ b/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/basic/BasicDemo.kt @@ -65,6 +65,10 @@ fun BasicDemo(onBackClick: () -> Unit) { ) { paddingValues -> val viewModel = remember { DemoViewModel() } val toastImpl = rememberPlatformToastImpl() + // KRelay keeps implementations weakly: hold strong references while this screen is shown + val notificationImpl = remember { MockNotificationImpl() } + val navigationImpl = remember { MockNavigationImpl() } + val analyticsImpl = remember { MockAnalyticsImpl() } // Register implementations for Basic Demo LaunchedEffect(Unit) { @@ -75,11 +79,11 @@ fun BasicDemo(onBackClick: () -> Unit) { println(" → Registering ToastFeature -> Platform-specific REAL implementation") KRelay.register(toastImpl) println(" → Registering NotificationBridge -> MockNotificationImpl") - KRelay.register(MockNotificationImpl()) + KRelay.register(notificationImpl) println(" → Registering NavigationFeature -> MockNavigationImpl") - KRelay.register(MockNavigationImpl()) + KRelay.register(navigationImpl) println(" → Registering AnalyticsFeature -> MockAnalyticsImpl") - KRelay.register(MockAnalyticsImpl()) + KRelay.register(analyticsImpl) println(" āœ“ All implementations registered!") println("\nšŸ’” NOTE:") println(" • ToastFeature -> REAL (Android Toast / iOS UIAlertController)") diff --git a/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/integrations/IntegrationsDemo.kt b/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/integrations/IntegrationsDemo.kt index c9994b4..70b54f0 100644 --- a/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/integrations/IntegrationsDemo.kt +++ b/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/integrations/IntegrationsDemo.kt @@ -52,50 +52,6 @@ fun IntegrationsDemo(onBackClick: () -> Unit) { // ALWAYS use REAL library implementations SetupRealIntegrations() - // Old mock implementation code removed - if (false) { - LaunchedEffect(Unit) { - println("\n╔════════════════════════════════════════════════════════════════╗") - println("ā•‘ šŸ”Œ INTEGRATIONS DEMO - Library Integration Setup ā•‘") - println("ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•") - println("\nšŸ”§ [IntegrationsDemo] Registering integration implementations...") - println(" → This demo shows KRelay as 'The Glue Code Standard'") - println(" → Demonstrating clean integration with:") - println(" • Moko Permissions (Permission management)") - println(" • Moko Biometry (Biometric auth)") - println(" • Play Core / StoreKit (In-app review)") - println(" • Peekaboo (Media picking)") - println("\n Registering implementations...") - - KRelay.register(MockPermissionImpl()) - println(" āœ“ PermissionFeature -> MockPermissionImpl (simulating Moko)") - - KRelay.register(MockBiometricImpl()) - println(" āœ“ BiometricFeature -> MockBiometricImpl (simulating Moko Biometry)") - - KRelay.register(MockSystemInteractionImpl()) - println(" āœ“ SystemInteractionFeature -> MockSystemInteractionImpl (Play Core/StoreKit)") - - KRelay.register(MockMediaImpl()) - println(" āœ“ MediaFeature -> MockMediaImpl (simulating Peekaboo)") - - // Supporting features - KRelay.register(MockToastImpl()) - KRelay.register(MockHapticImpl()) - KRelay.register(MockNavigationImpl()) - KRelay.register(MockAnalyticsImpl()) - - println("\nšŸ’” KEY INSIGHT:") - println(" ViewModel (IntegrationsViewModel) has ZERO dependencies on:") - println(" • Moko Permissions PermissionsController") - println(" • Moko Biometry BiometryManager") - println(" • Play Core ReviewManager") - println(" • Peekaboo rememberImagePickerLauncher") - println("\n āœ… Result: Clean architecture, no memory leaks, easy testing!") - println("═══════════════════════════════════════════════════════════════════\n") - } - } - Column( modifier = Modifier .fillMaxSize() diff --git a/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/integrations/RealIntegrations.kt b/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/integrations/RealIntegrations.kt index 7f7f565..ef06094 100644 --- a/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/integrations/RealIntegrations.kt +++ b/composeApp/src/commonMain/kotlin/dev/brewkits/krelay/integrations/RealIntegrations.kt @@ -23,6 +23,8 @@ fun SetupRealIntegrations() { val toastImpl = rememberToastImplementation() val hapticImpl = rememberHapticImplementation() val analyticsImpl = rememberAnalyticsImplementation() + // KRelay keeps implementations weakly: hold a strong reference for the composition's lifetime + val navigationImpl = remember { MockNavigationImpl() } LaunchedEffect(Unit) { println("\n╔════════════════════════════════════════════════════════════════╗") @@ -58,7 +60,7 @@ fun SetupRealIntegrations() { println(" āœ“ AnalyticsFeature -> Platform specific (REAL)") // Navigation feature - using mock (use VoyagerDemo for real navigation) - KRelay.register(MockNavigationImpl()) + KRelay.register(navigationImpl) println(" āœ“ NavigationFeature -> Mock (see VoyagerDemo for real navigation)") println("\n✨ All REAL integrations registered!") diff --git a/composeApp/src/iosMain/kotlin/dev/brewkits/krelay/integrations/IOSIntegrations.kt b/composeApp/src/iosMain/kotlin/dev/brewkits/krelay/integrations/IOSIntegrations.kt index 0a1a006..552c9df 100644 --- a/composeApp/src/iosMain/kotlin/dev/brewkits/krelay/integrations/IOSIntegrations.kt +++ b/composeApp/src/iosMain/kotlin/dev/brewkits/krelay/integrations/IOSIntegrations.kt @@ -6,6 +6,10 @@ import dev.icerock.moko.permissions.Permission import dev.icerock.moko.permissions.PermissionsController import dev.icerock.moko.permissions.compose.BindEffect import dev.icerock.moko.permissions.compose.rememberPermissionsControllerFactory +import dev.icerock.moko.permissions.camera.CAMERA +import dev.icerock.moko.permissions.gallery.GALLERY +import dev.icerock.moko.permissions.location.LOCATION +import dev.icerock.moko.permissions.microphone.RECORD_AUDIO import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2e773c5..cbed56a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ bcv = "0.17.0" material3 = "1.9.0" # Integration libraries -moko-permissions = "0.18.0" +moko-permissions = "0.20.1" moko-biometry = "0.4.0" peekaboo = "0.5.2" playcore-review = "2.0.1" @@ -48,6 +48,10 @@ compose-uiToolingPreview = { module = "org.jetbrains.compose.ui:ui-tooling-previ # Integration libraries moko-permissions = { module = "dev.icerock.moko:permissions", version.ref = "moko-permissions" } moko-permissions-compose = { module = "dev.icerock.moko:permissions-compose", version.ref = "moko-permissions" } +moko-permissions-camera = { module = "dev.icerock.moko:permissions-camera", version.ref = "moko-permissions" } +moko-permissions-location = { module = "dev.icerock.moko:permissions-location", version.ref = "moko-permissions" } +moko-permissions-microphone = { module = "dev.icerock.moko:permissions-microphone", version.ref = "moko-permissions" } +moko-permissions-gallery = { module = "dev.icerock.moko:permissions-gallery", version.ref = "moko-permissions" } moko-biometry = { module = "dev.icerock.moko:biometry", version.ref = "moko-biometry" } moko-biometry-compose = { module = "dev.icerock.moko:biometry-compose", version.ref = "moko-biometry" } peekaboo-ui = { module = "io.github.onseok:peekaboo-ui", version.ref = "peekaboo" } From 752875d879afa7760dbc7f829eb4bb96a8a26be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?= Date: Thu, 1 Oct 2026 01:50:36 +0700 Subject: [PATCH 7/8] perf/test: fix O(n^2) enqueue sweep, factory key collision; add performance, security and coverage tests; Kover; compose instrumented tests; docs --- CHANGELOG.md | 8 + docs/TESTING.md | 51 ++- gradle/libs.versions.toml | 7 +- krelay-compose/build.gradle.kts | 15 + .../krelay/compose/KRelayComposeTest.kt | 5 +- .../krelay/testing/FakeKRelayInstance.kt | 4 +- krelay/build.gradle.kts | 1 + .../kotlin/dev/brewkits/krelay/KRelay.kt | 5 +- .../dev/brewkits/krelay/KRelayInstanceImpl.kt | 42 +- .../krelay/performance/PerformanceTest.kt | 156 +++++++ .../brewkits/krelay/security/SecurityTest.kt | 283 +++++++++++++ .../BackgroundForegroundScenarioTest.kt | 11 +- .../brewkits/krelay/unit/CoverageGapTest.kt | 388 ++++++++++++++++++ 13 files changed, 940 insertions(+), 36 deletions(-) create mode 100644 krelay/src/commonTest/kotlin/dev/brewkits/krelay/performance/PerformanceTest.kt create mode 100644 krelay/src/commonTest/kotlin/dev/brewkits/krelay/security/SecurityTest.kt create mode 100644 krelay/src/commonTest/kotlin/dev/brewkits/krelay/unit/CoverageGapTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e57d95..e6b0132 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Metrics reporter**: `KRelayMetricsReporter` + `KRelayMetricEvent` / `KRelayMetricType`, registered via `KRelayMetrics.addReporter()` / `removeReporter()`. Receives dispatch, queue, replay, expiry and clear events for forwarding to Firebase Performance, Datadog or any custom backend. The core module stays dependency-free — no SDK exporters are bundled; see `docs/MONITORING.md`. - **`dispatchPersistedSuspend`** (`krelay-flow`): suspend overload of `dispatchPersisted` that runs factory lookup, queueing and the persistence write on `Dispatchers.Default`, so storage I/O never blocks the calling coroutine. +- **Test coverage tooling**: Kover is applied to `krelay` (`./gradlew :krelay:koverHtmlReport`). Core sources (excluding the bundled `samples` package) are at ~90% line coverage on JVM. +- **New test suites**: `performance/PerformanceTest` (throughput and scaling guards), `security/SecurityTest` (malformed/tampered persistence, key confusion, flooding, hostile callbacks) and `unit/CoverageGapTest` (singleton KClass entry points, error isolation, expiry, persistence restore edge cases, diagnostics). + ### Fixed +- **Quadratic enqueue cost**: every enqueue scanned the whole queue for expired actions and read the clock per element. Queueing 20k actions took ~66 s on the iOS simulator (milliseconds on JVM). The sweep now runs only when the oldest queued action can actually have expired (~0.05 s for the same workload). +- **Persisted-action factory key collision**: factories were looked up by `"$featureKey::$actionKey"`, so `("a", "b::c")` and `("a::b", "c")` shared one slot. Keys are now length-prefixed. +- **`KRelay.getMetrics`-style internal helper omitted `cleared`**: `getMetricsInternal` (used for iOS interop) now reports the same five counters as `getMetrics()`. +- **`krelay-compose` instrumented tests did not compile** (missing test dependencies and runner); they now run (`connectedDebugAndroidTest`). +- **Demo app**: implementations registered inline were garbage-collected (KRelay holds them weakly), so dispatches were queued; the selected demo was lost on rotation; `moko-permissions` 0.18.0 broke the Kotlin/Native 2.3 iOS link and was upgraded to 0.20.1 (modular API). - **Per-instance metrics were never populated**: `KRelayInstanceImpl` recorded metrics without its `scopeName`, so `KRelayMetrics.getInstanceMetrics(scope)` always returned an empty map. Dispatch, queue, replay and clear events are now attributed to their instance. - **Expired actions were never counted**: `recordExpiry` was never called; expired actions dropped on replay are now recorded. diff --git a/docs/TESTING.md b/docs/TESTING.md index 9a358ec..50dbf25 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -51,7 +51,13 @@ Test complete real-world scenarios: **Purpose**: Verify the entire system works in production scenarios. -### 4. Demo Examples (`krelay/src/commonTest/kotlin/dev/brewkits/krelay/demo/`) +### 4. Stress, Performance & Security Tests + +- **stress/** (`LockStressTest`, `ScopeTokenConcurrentStressTest`) - heavy concurrent access +- **performance/PerformanceTest.kt** - throughput and scaling guards (20k-queue enqueue/replay, priority insertion, flood, churn). Budgets are generous; they catch algorithmic regressions such as O(n²) queueing, not micro-variance. +- **security/SecurityTest.kt** - malformed and tampered persisted commands, factory-key confusion, queue flooding, misbehaving callbacks, token uniqueness, instance isolation + +### 5. Demo Examples (`krelay/src/commonTest/kotlin/dev/brewkits/krelay/demo/`) Advanced usage examples that double as tests: @@ -69,46 +75,56 @@ Advanced usage examples that double as tests: ### Run All Tests ```bash -./gradlew :krelay:test +./gradlew :krelay:jvmTest ``` ### Run Specific Test Class ```bash -./gradlew :krelay:test --tests "dev.brewkits.krelay.unit.WeakRefTest" -./gradlew :krelay:test --tests "dev.brewkits.krelay.demo.LoginFlowDemo" +./gradlew :krelay:jvmTest --tests "dev.brewkits.krelay.unit.WeakRefTest" +./gradlew :krelay:jvmTest --tests "dev.brewkits.krelay.demo.LoginFlowDemo" ``` ### Run Tests by Category ```bash # Unit tests only -./gradlew :krelay:test --tests "dev.brewkits.krelay.unit.*" +./gradlew :krelay:jvmTest --tests "dev.brewkits.krelay.unit.*" # Integration tests only -./gradlew :krelay:test --tests "dev.brewkits.krelay.integration.*" +./gradlew :krelay:jvmTest --tests "dev.brewkits.krelay.integration.*" # System tests only -./gradlew :krelay:test --tests "dev.brewkits.krelay.system.*" +./gradlew :krelay:jvmTest --tests "dev.brewkits.krelay.system.*" + +# Stress / performance / security +./gradlew :krelay:jvmTest --tests "dev.brewkits.krelay.stress.*" --tests "dev.brewkits.krelay.performance.*" --tests "dev.brewkits.krelay.security.*" # Demo examples only -./gradlew :krelay:test --tests "dev.brewkits.krelay.demo.*" +./gradlew :krelay:jvmTest --tests "dev.brewkits.krelay.demo.*" ``` +Per platform: `:krelay:jvmTest`, `:krelay:testDebugUnitTest`, `:krelay:wasmJsBrowserTest`, `:krelay:iosSimulatorArm64Test`. +On-device tests: `ANDROID_SERIAL= ./gradlew :krelay:connectedDebugAndroidTest :krelay-compose:connectedDebugAndroidTest`. + ### Run with Debug Output ```bash -./gradlew :krelay:test --info -./gradlew :krelay:test --debug +./gradlew :krelay:jvmTest --info +./gradlew :krelay:jvmTest --debug ``` ### Test Coverage Report +Coverage is measured with [Kover](https://github.com/Kotlin/kotlinx-kover) (JVM): + ```bash -./gradlew :krelay:test :krelay:jacocoTestReport -# Report will be in: krelay/build/reports/jacoco/test/html/index.html +./gradlew :krelay:koverHtmlReport +# Report: krelay/build/reports/kover/html/index.html (XML: :krelay:koverXmlReport) ``` +The bundled `dev.brewkits.krelay.samples` package (demo view models) is intentionally not unit tested. + --- ## Test Utilities @@ -777,12 +793,13 @@ When adding new features: Add to CI pipeline: ```bash -# In CI script -./gradlew :krelay:test --continue -./gradlew :krelay:jacocoTestReport +# See .github/workflows/ci.yml for the full pipeline +./gradlew :krelay:testDebugUnitTest :krelay:jvmTest :krelay:wasmJsBrowserTest --continue +./gradlew :krelay:iosSimulatorArm64Test # macOS runner +./gradlew :krelay:apiCheck :krelay-compose:apiCheck -# Fail if coverage < 80% -./gradlew :krelay:jacocoTestCoverageVerification +# Optional: coverage report +./gradlew :krelay:koverXmlReport ``` --- diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cbed56a..0bf5fa1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,10 +10,12 @@ androidx-core = "1.17.0" androidx-espresso = "3.7.0" androidx-lifecycle = "2.9.6" androidx-testExt = "1.3.0" +androidx-composeUiTest = "1.10.6" composeMultiplatform = "1.10.3" junit = "4.13.2" kotlin = "2.3.21" bcv = "0.17.0" +kover = "0.9.11" material3 = "1.9.0" # Integration libraries @@ -31,6 +33,8 @@ kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotl kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } junit = { module = "junit:junit", version.ref = "junit" } androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } +androidx-compose-uiTest-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-composeUiTest" } +androidx-compose-uiTest-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-composeUiTest" } androidx-testExt-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-testExt" } androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } @@ -72,4 +76,5 @@ composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "k kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } -bcv = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "bcv" } \ No newline at end of file +bcv = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "bcv" } +kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } diff --git a/krelay-compose/build.gradle.kts b/krelay-compose/build.gradle.kts index 351e140..902a762 100644 --- a/krelay-compose/build.gradle.kts +++ b/krelay-compose/build.gradle.kts @@ -47,6 +47,15 @@ kotlin { commonTest.dependencies { implementation(libs.kotlin.test) } + androidInstrumentedTest.dependencies { + implementation(libs.kotlin.test) + implementation(libs.kotlin.testJunit) + implementation(libs.androidx.testExt.junit) + implementation(libs.androidx.espresso.core) + implementation(libs.androidx.compose.uiTest.junit4) + implementation(libs.compose.runtime) + implementation(libs.compose.ui) + } } } @@ -55,6 +64,7 @@ android { compileSdk = libs.versions.android.compileSdk.get().toInt() defaultConfig { minSdk = libs.versions.android.minSdk.get().toInt() + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } compileOptions { @@ -147,3 +157,8 @@ signing { tasks.withType().configureEach { mustRunAfter(tasks.withType()) } + +dependencies { + // Provides the ComponentActivity host required by createComposeRule() + debugImplementation(libs.androidx.compose.uiTest.manifest) +} diff --git a/krelay-compose/src/androidInstrumentedTest/kotlin/dev/brewkits/krelay/compose/KRelayComposeTest.kt b/krelay-compose/src/androidInstrumentedTest/kotlin/dev/brewkits/krelay/compose/KRelayComposeTest.kt index 514e6c0..2548348 100644 --- a/krelay-compose/src/androidInstrumentedTest/kotlin/dev/brewkits/krelay/compose/KRelayComposeTest.kt +++ b/krelay-compose/src/androidInstrumentedTest/kotlin/dev/brewkits/krelay/compose/KRelayComposeTest.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.test.junit4.createComposeRule import dev.brewkits.krelay.KRelay import dev.brewkits.krelay.KRelayInstance import dev.brewkits.krelay.RelayFeature +import dev.brewkits.krelay.dispatch import org.junit.Rule import org.junit.Test import kotlin.test.AfterTest @@ -74,7 +75,7 @@ class KRelayComposeTest { // Initially key is 1 var label = "" - krelay.dispatch(DummyComposeFeature::class) { + krelay.dispatch { label = it.getLabel() } composeTestRule.waitForIdle() @@ -84,7 +85,7 @@ class KRelayComposeTest { key = 2 composeTestRule.waitForIdle() - krelay.dispatch(DummyComposeFeature::class) { + krelay.dispatch { label = it.getLabel() } composeTestRule.waitForIdle() diff --git a/krelay-testing/src/commonMain/kotlin/dev/brewkits/krelay/testing/FakeKRelayInstance.kt b/krelay-testing/src/commonMain/kotlin/dev/brewkits/krelay/testing/FakeKRelayInstance.kt index f0038e4..ea752c7 100644 --- a/krelay-testing/src/commonMain/kotlin/dev/brewkits/krelay/testing/FakeKRelayInstance.kt +++ b/krelay-testing/src/commonMain/kotlin/dev/brewkits/krelay/testing/FakeKRelayInstance.kt @@ -82,11 +82,11 @@ class FakeKRelayInstance : KRelayInstance { payload: String, priorityValue: Int ) { - // No-op for now in test fake + // The fake does not simulate persistence; persisted dispatches are not recorded } override fun setPersistenceAdapter(adapter: dev.brewkits.krelay.KRelayPersistenceAdapter) { - // No-op for test fake + // The fake does not simulate persistence } override fun restorePersistedActions() { diff --git a/krelay/build.gradle.kts b/krelay/build.gradle.kts index dc127a8..bf6a64d 100644 --- a/krelay/build.gradle.kts +++ b/krelay/build.gradle.kts @@ -7,6 +7,7 @@ plugins { alias(libs.plugins.androidLibrary) alias(libs.plugins.dokka) alias(libs.plugins.bcv) + alias(libs.plugins.kover) id("maven-publish") id("signing") } diff --git a/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelay.kt b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelay.kt index 890b4ec..597280b 100644 --- a/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelay.kt +++ b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelay.kt @@ -58,8 +58,6 @@ object KRelay { // SINGLETON API (v1.0 - Backward Compatible) // ============================================================ - // Internal state accessors removed for M-03. Always use defaultInstance. - /** * Global configuration: Maximum pending actions allowed per feature type. * Default: 100 @@ -318,7 +316,8 @@ object KRelay { "dispatches" to KRelayMetrics.getDispatchCount(kClass), "queued" to KRelayMetrics.getQueueCount(kClass), "replayed" to KRelayMetrics.getReplayCount(kClass), - "expired" to KRelayMetrics.getExpiryCount(kClass) + "expired" to KRelayMetrics.getExpiryCount(kClass), + "cleared" to KRelayMetrics.getClearCount(kClass) ) } diff --git a/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayInstanceImpl.kt b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayInstanceImpl.kt index cfaf8b4..fb1fe4e 100644 --- a/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayInstanceImpl.kt +++ b/krelay/src/commonMain/kotlin/dev/brewkits/krelay/KRelayInstanceImpl.kt @@ -40,6 +40,14 @@ internal class KRelayInstanceImpl( @PublishedApi internal val pendingQueue = mutableMapOf, MutableList>() + /** + * Per-feature lower bound of the oldest timestamp in [pendingQueue]. Lets [enqueueActionUnderLock] + * skip the expiry sweep (an O(n) scan) unless something in the queue can actually have expired. + * Removals only raise the true minimum, so a stale value is still a valid lower bound; + * a missing value with a non-empty queue means "unknown" and forces a sweep. + */ + private val oldestEnqueuedMs = mutableMapOf, Long>() + /** * The current persistence engine. Defaults to an in-memory mock. */ @@ -52,6 +60,13 @@ internal class KRelayInstanceImpl( @PublishedApi internal val actionFactories = mutableMapOf>() + /** + * Length-prefixed so that no two (featureKey, actionKey) pairs can collide, e.g. + * ("a", "b::c") vs ("a::b", "c") would both flatten to "a::b::c" with a plain separator. + */ + private fun factoryKeyOf(featureKey: String, actionKey: String): String = + "${featureKey.length}:$featureKey$actionKey" + /** * Reverse mapping from stable string keys to [KClass] objects. */ @@ -204,7 +219,13 @@ internal class KRelayInstanceImpl( evictByPriority: Boolean = false ) { val queue = pendingQueue.getOrPut(kClass) { mutableListOf() } - queue.removeAll { it.isExpired(actionExpiryMs) } + val now = currentTimeMillis() + val oldest = oldestEnqueuedMs[kClass] + if (queue.isNotEmpty() && (oldest == null || now - oldest > actionExpiryMs)) { + queue.removeAll { now - it.timestampMs > actionExpiryMs } + val remainingOldest = queue.minOfOrNull { it.timestampMs } + if (remainingOldest != null) oldestEnqueuedMs[kClass] = remainingOldest else oldestEnqueuedMs.remove(kClass) + } if (queue.size >= maxQueueSize) { if (evictByPriority && queue.isNotEmpty()) { @@ -227,6 +248,12 @@ internal class KRelayInstanceImpl( } else { queue.add(action) } + noteEnqueued(kClass, action.timestampMs) + } + + private fun noteEnqueued(kClass: KClass<*>, timestampMs: Long) { + val current = oldestEnqueuedMs[kClass] + if (current == null || timestampMs < current) oldestEnqueuedMs[kClass] = timestampMs } /** @@ -406,6 +433,7 @@ internal class KRelayInstanceImpl( } registry.clear() pendingQueue.clear() + oldestEnqueuedMs.clear() actionFactories.clear() featureKeyToKClass.clear() _persistenceAdapter @@ -430,7 +458,7 @@ internal class KRelayInstanceImpl( lock.withLock { featureKeyToKClass[featureKey] = kClass @Suppress("UNCHECKED_CAST") - actionFactories["$featureKey::$actionKey"] = factory as ActionFactory<*> + actionFactories[factoryKeyOf(featureKey, actionKey)] = factory as ActionFactory<*> if (debugMode) { log("[FACTORY] Registered factory for $featureKey::$actionKey") } @@ -455,13 +483,14 @@ internal class KRelayInstanceImpl( payload: String, priorityValue: Int ) { - val factoryKey = "$featureKey::$actionKey" + val factoryKey = factoryKeyOf(featureKey, actionKey) + val displayKey = "$featureKey::$actionKey" // Resolve factory and reconstruct block before acquiring the main lock. // factory() only produces a lambda — no I/O, safe to call outside lock. val factory = lock.withLock { actionFactories[factoryKey] } as? ActionFactory ?: error( - "No factory registered for '$factoryKey'. " + + "No factory registered for '$displayKey'. " + "Call instance.registerActionFactory<$featureKey>(\"$actionKey\") { payload -> { feature -> ... } } first." ) val block = factory(payload) @@ -566,11 +595,11 @@ internal class KRelayInstanceImpl( return@commandLoop } - val factoryKey = "$featureKey::${command.actionKey}" + val factoryKey = factoryKeyOf(featureKey, command.actionKey) @Suppress("UNCHECKED_CAST") val factory = actionFactories[factoryKey] as? ActionFactory if (factory == null) { - if (debugMode) logWarn("No factory for '$factoryKey'. Skipping restored action.") + if (debugMode) logWarn("No factory for '$featureKey::${command.actionKey}'. Skipping restored action.") skippedNoFactory++ toRemove.add(EnqueuedEntry(featureKey, command)) return@commandLoop @@ -584,6 +613,7 @@ internal class KRelayInstanceImpl( val insertIndex = queue.binarySearch { queuedAction.priority.compareTo(it.priority) } .let { if (it < 0) -(it + 1) else it } queue.add(insertIndex, queuedAction) + noteEnqueued(kClass, queuedAction.timestampMs) toRemove.add(EnqueuedEntry(featureKey, command)) restoredCount++ diff --git a/krelay/src/commonTest/kotlin/dev/brewkits/krelay/performance/PerformanceTest.kt b/krelay/src/commonTest/kotlin/dev/brewkits/krelay/performance/PerformanceTest.kt new file mode 100644 index 0000000..2773a8b --- /dev/null +++ b/krelay/src/commonTest/kotlin/dev/brewkits/krelay/performance/PerformanceTest.kt @@ -0,0 +1,156 @@ +package dev.brewkits.krelay.performance + +import dev.brewkits.krelay.* +import kotlin.test.* + +/** + * Coarse performance guards. Budgets are deliberately generous (CI machines, iOS simulator, + * Kotlin/Wasm) — they catch algorithmic regressions (e.g. O(n²) queueing), not micro-variance. + */ +class PerformanceTest { + + interface CounterFeature : RelayFeature { + fun hit() + } + + class Counter : CounterFeature { + var count = 0 + override fun hit() { count++ } + } + + @BeforeTest + fun setup() { + KRelay.reset() + KRelayMetrics.reset() + KRelayMetrics.enabled = false + } + + @AfterTest + fun tearDown() { + KRelay.reset() + KRelay.clearInstanceRegistry() + KRelayMetrics.reset() + KRelayMetrics.enabled = false + } + + private inline fun elapsedMs(block: () -> Unit): Long { + val start = currentTimeMillis() + block() + return currentTimeMillis() - start + } + + @Test + fun testImmediateDispatchThroughput() { + val instance = KRelay.create("PerfDispatch") + val counter = Counter() + instance.register(counter) + + val ms = elapsedMs { repeat(50_000) { instance.dispatch { it.hit() } } } + + assertEquals(50_000, counter.count) + assertTrue(ms < 15_000, "50k immediate dispatches took ${ms}ms") + instance.reset() + } + + @Test + fun testQueueAndReplayScale() { + val instance = KRelay.builder("PerfReplay").maxQueueSize(20_000).build() + val counter = Counter() + + val queueMs = elapsedMs { repeat(20_000) { instance.dispatch { it.hit() } } } + assertEquals(20_000, instance.getPendingCount()) + + val replayMs = elapsedMs { instance.register(counter) } + + assertEquals(20_000, counter.count) + assertTrue(queueMs < 15_000, "queueing 20k actions took ${queueMs}ms") + assertTrue(replayMs < 15_000, "replaying 20k actions took ${replayMs}ms") + instance.reset() + } + + @Test + fun testPriorityInsertionScalesWithMixedPriorities() { + val instance = KRelay.builder("PerfPriority").maxQueueSize(20_000).build() + val priorities = ActionPriority.values() + + val ms = elapsedMs { + repeat(20_000) { i -> + instance.dispatchWithPriority(priorities[i % priorities.size]) { it.hit() } + } + } + + assertEquals(20_000, instance.getPendingCount()) + assertTrue(ms < 15_000, "20k priority inserts took ${ms}ms") + instance.reset() + } + + @Test + fun testFloodAgainstFullQueueStaysBoundedAndFast() { + val instance = KRelay.builder("PerfFlood").maxQueueSize(100).build() + + val ms = elapsedMs { repeat(100_000) { instance.dispatch { it.hit() } } } + + assertEquals(100, instance.getPendingCount()) + assertTrue(ms < 20_000, "100k dispatches against a full queue took ${ms}ms") + instance.reset() + } + + @Test + fun testRegisterUnregisterChurnDoesNotAccumulateState() { + val instance = KRelay.create("PerfChurn") + + val ms = elapsedMs { + repeat(10_000) { + val counter = Counter() + instance.register(counter) + instance.unregister() + } + } + + assertEquals(0, instance.getPendingCount()) + assertFalse(instance.isRegistered()) + assertEquals(0, instance.getRegisteredFeaturesCount()) + assertTrue(ms < 15_000, "10k register/unregister cycles took ${ms}ms") + instance.reset() + } + + @Test + fun testManyInstancesCreateAndRemove() { + val ms = elapsedMs { + repeat(1_000) { i -> KRelay.create("Perf-$i") } + repeat(1_000) { i -> KRelay.removeInstance("Perf-$i") } + } + + assertTrue(ms < 15_000, "1000 instance create/remove took ${ms}ms") + } + + @Test + fun testMetricsAndReporterOverheadIsBounded() { + KRelayMetrics.enabled = true + var events = 0 + val reporter = KRelayMetricsReporter { events++ } + KRelayMetrics.addReporter(reporter) + val instance = KRelay.create("PerfMetrics") + instance.register(Counter()) + + val ms = elapsedMs { repeat(50_000) { instance.dispatch { it.hit() } } } + + KRelayMetrics.removeReporter(reporter) + assertEquals(50_000, events) + assertEquals(50_000L, KRelayMetrics.getDispatchCount(CounterFeature::class)) + assertTrue(ms < 20_000, "50k dispatches with metrics+reporter took ${ms}ms") + instance.reset() + } + + @Test + fun testPersistedCommandSerializationThroughput() { + val payload = "x".repeat(1_000) + val ms = elapsedMs { + repeat(20_000) { i -> + val restored = PersistedCommand.deserialize(PersistedCommand("action-$i", payload, 1L, 50).serialize()) + assertNotNull(restored) + } + } + assertTrue(ms < 15_000, "20k serialize/deserialize round trips took ${ms}ms") + } +} diff --git a/krelay/src/commonTest/kotlin/dev/brewkits/krelay/security/SecurityTest.kt b/krelay/src/commonTest/kotlin/dev/brewkits/krelay/security/SecurityTest.kt new file mode 100644 index 0000000..217f964 --- /dev/null +++ b/krelay/src/commonTest/kotlin/dev/brewkits/krelay/security/SecurityTest.kt @@ -0,0 +1,283 @@ +package dev.brewkits.krelay.security + +import dev.brewkits.krelay.* +import kotlin.test.* + +/** + * Robustness against hostile or corrupted input: tampered persistence, malformed serialized + * commands, flooding, key confusion, and misbehaving callbacks. + */ +class SecurityTest { + + interface ToastFeature : RelayFeature { + fun show(message: String) + } + + interface PayFeature : RelayFeature { + fun pay(message: String) + } + + class MockToast : ToastFeature { + val shown = mutableListOf() + override fun show(message: String) { shown.add(message) } + } + + class MockPay : PayFeature { + val paid = mutableListOf() + override fun pay(message: String) { paid.add(message) } + } + + private class FixedAdapter(private val data: Map>) : KRelayPersistenceAdapter { + val removed = mutableListOf() + override fun save(scopeName: String, featureKey: String, command: PersistedCommand) = Unit + override fun loadAll(scopeName: String) = data + override fun remove(scopeName: String, featureKey: String, command: PersistedCommand) { removed.add(command) } + override fun clearScope(scopeName: String) = Unit + override fun clearAll() = Unit + } + + @BeforeTest + fun setup() { + KRelay.reset() + KRelayLog.sink = KRelayLogSink { _, _, _ -> } + } + + @AfterTest + fun tearDown() { + KRelayLog.reset() + KRelay.reset() + KRelay.clearInstanceRegistry() + } + + // ── malformed serialized commands ──────────────────────────────── + + @Test + fun testDeserialize_rejectsMalformedInputWithoutThrowing() { + val hostile = listOf( + "", + ":", + "::", + ":::", + "abc:def:ghi:jkl", + "1:2:3", + "1:2:-5:abc", + "1:2:999999999:short", + "99999999999999999999:1:1:a", + "1:99999999999:1:a", + "1:2:3:ab", + "\u0000:\u0000:\u0000:\u0000", + "1:2:-2147483648:x", + ) + hostile.forEach { input -> + assertNull(PersistedCommand.deserialize(input), "expected null for ${input.take(30)}") + } + } + + @Test + fun testSerialize_roundTripsHostilePayloadsWithoutChangingBoundaries() { + val payloads = listOf( + "", + ":::", + "1:2:3:4", + "line1\nline2\r\n", + "\u0000\u0001", + "ę—„ęœ¬čŖž šŸ˜€", + "::", + "a".repeat(100_000), + ) + payloads.forEach { payload -> + val original = PersistedCommand("act:ion::key", payload, 42L, 7) + val restored = PersistedCommand.deserialize(original.serialize()) + assertEquals(original, restored) + } + } + + // ── tampered storage ───────────────────────────────────────────── + + @Test + fun testRestore_dropsCommandsThatReferenceUnregisteredFactories() { + val now = currentTimeMillis() + val tampered = mapOf( + "toast" to listOf(PersistedCommand("pay_everything", "x", now)), + "admin" to listOf(PersistedCommand("grant", "x", now)) + ) + val adapter = FixedAdapter(tampered) + val instance = KRelay.create("TamperedScope") + instance.setPersistenceAdapter(adapter) + instance.registerActionFactory("toast", "show") { p -> { it.show(p) } } + val mock = MockToast() + instance.register(mock) + + instance.restorePersistedActions() + + assertEquals(0, instance.getPendingCount()) + assertTrue(mock.shown.isEmpty()) + assertEquals(2, adapter.removed.size, "tampered entries must be purged from storage") + instance.reset() + } + + @Test + fun testRestore_doesNotDeliverToADifferentFeature() { + val now = currentTimeMillis() + val adapter = FixedAdapter(mapOf("toast" to listOf(PersistedCommand("pay", "1000", now)))) + val instance = KRelay.create("CrossFeatureScope") + instance.setPersistenceAdapter(adapter) + // "pay" factory exists, but only under the *pay* feature key + instance.registerActionFactory("pay", "pay") { p -> { it.pay(p) } } + instance.registerActionFactory("toast", "show") { p -> { it.show(p) } } + val pay = MockPay() + val toast = MockToast() + instance.register(pay) + instance.register(toast) + + instance.restorePersistedActions() + + assertTrue(pay.paid.isEmpty()) + assertTrue(toast.shown.isEmpty()) + instance.reset() + } + + @Test + fun testRestore_expiredCommandsAreNeverExecuted() { + val stale = PersistedCommand("show", "old", currentTimeMillis() - 10 * 60_000) + val adapter = FixedAdapter(mapOf("toast" to listOf(stale))) + val instance = KRelay.builder("StaleScope").actionExpiry(60_000).build() + instance.setPersistenceAdapter(adapter) + instance.registerActionFactory("toast", "show") { p -> { it.show(p) } } + val mock = MockToast() + instance.register(mock) + + instance.restorePersistedActions() + + assertTrue(mock.shown.isEmpty()) + assertEquals(1, adapter.removed.size) + instance.reset() + } + + // ── key confusion ──────────────────────────────────────────────── + + @Test + fun testFactoryKeys_doNotCollideAcrossFeatureAndActionBoundaries() { + val instance = KRelay.create("KeyConfusionScope") + val toast = MockToast() + val pay = MockPay() + instance.register(toast) + instance.register(pay) + // ("a", "b::c") and ("a::b", "c") would both flatten to "a::b::c" with a naive separator. + instance.registerActionFactory("a", "b::c") { p -> { it.show("toast:$p") } } + instance.registerActionFactory("a::b", "c") { p -> { it.pay("pay:$p") } } + + instance.dispatchPersisted("a", "b::c", "1") + instance.dispatchPersisted("a::b", "c", "2") + + assertEquals(listOf("toast:1"), toast.shown) + assertEquals(listOf("pay:2"), pay.paid) + instance.reset() + } + + // ── flooding / resource exhaustion ─────────────────────────────── + + @Test + fun testQueueFlood_isBoundedByMaxQueueSize() { + val instance = KRelay.builder("FloodScope").maxQueueSize(50).build() + repeat(200_000) { instance.dispatch { it.show("spam") } } + + assertEquals(50, instance.getPendingCount()) + instance.reset() + } + + @Test + fun testCriticalActionsSurviveLowPriorityFlood() { + val instance = KRelay.builder("PriorityFloodScope").maxQueueSize(10).build() + instance.dispatchWithPriority(ActionPriority.CRITICAL) { it.show("critical") } + repeat(1_000) { instance.dispatchWithPriority(ActionPriority.LOW) { it.show("spam") } } + val mock = MockToast() + + instance.register(mock) + + assertTrue(mock.shown.contains("critical"), "critical action must not be evicted by low-priority spam") + assertEquals(10, mock.shown.size) + instance.reset() + } + + @Test + fun testOversizedPayloadRoundTrips() { + val payload = "p".repeat(1_000_000) + val restored = PersistedCommand.deserialize(PersistedCommand("k", payload, 1L, 1).serialize()) + assertEquals(payload.length, restored?.payload?.length) + } + + // ── misbehaving callbacks ──────────────────────────────────────── + + @Test + fun testThrowingActionDoesNotBlockSubsequentDispatches() { + val instance = KRelay.create("ThrowingScope") + val mock = MockToast() + instance.register(mock) + + instance.dispatch { error("malicious") } + instance.dispatch { it.show("still alive") } + + assertEquals(listOf("still alive"), mock.shown) + instance.reset() + } + + @Test + fun testThrowingReporterAndSinkDoNotBreakDispatch() { + KRelayMetrics.enabled = true + val bad = KRelayMetricsReporter { throw IllegalStateException("reporter") } + KRelayMetrics.addReporter(bad) + KRelayLog.sink = KRelayLogSink { _, _, _ -> throw IllegalStateException("sink") } + try { + val instance = KRelay.builder("HostileCallbacksScope").debugMode(true).build() + val mock = MockToast() + instance.register(mock) + instance.dispatch { it.show("ok") } + assertEquals(listOf("ok"), mock.shown) + instance.reset() + } finally { + KRelayMetrics.removeReporter(bad) + KRelayMetrics.reset() + KRelayMetrics.enabled = false + } + } + + // ── identifiers ────────────────────────────────────────────────── + + @Test + fun testScopeTokensAreUnique() { + val tokens = HashSet() + repeat(20_000) { assertTrue(tokens.add(scopedToken())) } + } + + @Test + fun testCancelScopeOnlyAffectsItsOwnToken() { + val instance = KRelay.create("TokenScope") + val mine = scopedToken() + val other = scopedToken() + instance.dispatch(mine) { it.show("mine") } + instance.dispatch(other) { it.show("other") } + + instance.cancelScope(mine) + val mock = MockToast() + instance.register(mock) + + assertEquals(listOf("other"), mock.shown) + instance.reset() + } + + @Test + fun testInstancesAreIsolated() { + val a = KRelay.create("IsolationA") + val b = KRelay.create("IsolationB") + val mockA = MockToast() + a.register(mockA) + + b.dispatch { it.show("for-b") } + + assertTrue(mockA.shown.isEmpty()) + assertEquals(1, b.getPendingCount()) + a.reset() + b.reset() + } +} diff --git a/krelay/src/commonTest/kotlin/dev/brewkits/krelay/system/BackgroundForegroundScenarioTest.kt b/krelay/src/commonTest/kotlin/dev/brewkits/krelay/system/BackgroundForegroundScenarioTest.kt index 00ba5d0..df75a53 100644 --- a/krelay/src/commonTest/kotlin/dev/brewkits/krelay/system/BackgroundForegroundScenarioTest.kt +++ b/krelay/src/commonTest/kotlin/dev/brewkits/krelay/system/BackgroundForegroundScenarioTest.kt @@ -3,6 +3,7 @@ package dev.brewkits.krelay.system import dev.brewkits.krelay.ActionPriority import dev.brewkits.krelay.KRelay import dev.brewkits.krelay.RelayFeature +import dev.brewkits.krelay.currentTimeMillis import dev.brewkits.krelay.dispatchWithPriority import kotlin.test.* @@ -141,12 +142,12 @@ class BackgroundForegroundScenarioTest { assertEquals(1, KRelay.getPendingCount()) - // Step 4: Wait for expiry (in real test with time control) - // For now, verify expiry mechanism exists + // Step 4: Wait until the action is past its 100ms expiry (busy wait: no sleep in common code) + val start = currentTimeMillis() + while (currentTimeMillis() - start < 150) { /* spin */ } - // Step 5: Check that getPendingCount removes expired - val count = KRelay.getPendingCount() - assertTrue(count >= 0) + // Step 5: Expired actions are no longer pending + assertEquals(0, KRelay.getPendingCount()) // Restore default KRelay.actionExpiryMs = 5 * 60 * 1000 diff --git a/krelay/src/commonTest/kotlin/dev/brewkits/krelay/unit/CoverageGapTest.kt b/krelay/src/commonTest/kotlin/dev/brewkits/krelay/unit/CoverageGapTest.kt new file mode 100644 index 0000000..e854089 --- /dev/null +++ b/krelay/src/commonTest/kotlin/dev/brewkits/krelay/unit/CoverageGapTest.kt @@ -0,0 +1,388 @@ +package dev.brewkits.krelay.unit + +import dev.brewkits.krelay.* +import kotlin.test.* + +/** + * Covers branches not reached by the feature-focused suites: singleton KClass entry points, + * error paths, expiry on replay, persistence restore edge cases and diagnostics output. + */ +@Suppress("DEPRECATION") +class CoverageGapTest { + + interface ToastFeature : RelayFeature { + fun show(message: String) + } + + class MockToast : ToastFeature { + val shown = mutableListOf() + override fun show(message: String) { shown.add(message) } + } + + class OtherToast : ToastFeature { + override fun show(message: String) {} + } + + private class Recorder : KRelayPersistenceAdapter { + val store = mutableMapOf>() + val removed = mutableListOf() + override fun save(scopeName: String, featureKey: String, command: PersistedCommand) { + store.getOrPut(featureKey) { mutableListOf() }.add(command) + } + override fun loadAll(scopeName: String): Map> = + store.mapValues { it.value.toList() } + override fun remove(scopeName: String, featureKey: String, command: PersistedCommand) { + removed.add(command) + store[featureKey]?.remove(command) + } + override fun clearScope(scopeName: String) = store.clear() + override fun clearAll() = store.clear() + } + + private val logs = mutableListOf>() + private val events = mutableListOf() + private val reporter = KRelayMetricsReporter { events.add(it) } + + @BeforeTest + fun setup() { + KRelay.reset() + KRelay.resetConfiguration() + KRelayMetrics.reset() + KRelayMetrics.enabled = true + KRelayMetrics.addReporter(reporter) + KRelayLog.sink = KRelayLogSink { level, _, message -> logs.add(level to message) } + } + + @AfterTest + fun tearDown() { + KRelayMetrics.removeReporter(reporter) + KRelayMetrics.reset() + KRelayMetrics.enabled = false + KRelayLog.reset() + KRelay.reset() + KRelay.resetConfiguration() + KRelay.clearInstanceRegistry() + } + + private fun spinFor(ms: Long) { + val start = currentTimeMillis() + while (currentTimeMillis() - start < ms) { /* busy wait: no sleep in common code */ } + } + + // ── singleton KClass entry points ──────────────────────────────── + + @Test + fun testSingletonKClassEntryPoints() { + val mock = MockToast() + KRelay.registerInternal(ToastFeature::class, mock) + assertTrue(KRelay.isRegisteredInternal(ToastFeature::class)) + + KRelay.dispatchInternal(ToastFeature::class) { it.show("a") } + KRelay.dispatchWithPriorityInternal(ToastFeature::class, ActionPriority.HIGH) { it.show("b") } + assertEquals(listOf("a", "b"), mock.shown) + + KRelay.unregisterInternal(ToastFeature::class) + assertFalse(KRelay.isRegisteredInternal(ToastFeature::class)) + + KRelay.dispatchInternal(ToastFeature::class) { it.show("queued") } + assertEquals(1, KRelay.getPendingCountInternal(ToastFeature::class)) + + KRelay.clearQueueInternal(ToastFeature::class) + assertEquals(0, KRelay.getPendingCountInternal(ToastFeature::class)) + + val metrics = KRelay.getMetricsInternal(ToastFeature::class) + assertEquals(setOf("dispatches", "queued", "replayed", "expired", "cleared"), metrics.keys) + assertEquals(2L, metrics["dispatches"]) + assertEquals(1L, metrics["cleared"]) + } + + @Test + fun testPriorityDispatchWithScopeToken_singletonAndInstance() { + val mock = MockToast() + val token = scopedToken() + + KRelay.dispatchWithPriority(ActionPriority.LOW, token) { it.show("queued-low") } + assertEquals(1, KRelay.getPendingCount()) + KRelay.cancelScope(token) + assertEquals(0, KRelay.getPendingCount()) + + KRelay.register(mock) + KRelay.dispatchWithPriority(ActionPriority.HIGH) { it.show("now") } + + val instance = KRelay.create("CoverageScope") + val instToken = scopedToken() + instance.dispatchWithPriority(ActionPriority.CRITICAL, instToken) { it.show("x") } + assertEquals(1, instance.getPendingCount()) + instance.cancelScope(instToken) + assertEquals(0, instance.getPendingCount()) + assertEquals(listOf("now"), mock.shown) + } + + // ── logging & warning branches ─────────────────────────────────── + + @Test + fun testOverwriteWithDifferentType_warnsInDebugMode() { + val instance = KRelay.builder("OverwriteScope").debugMode(true).build() + instance.register(MockToast()) + val keep = OtherToast() + instance.register(keep) + + assertTrue(logs.any { it.first == KRelayLogLevel.WARN && it.second.contains("Overwriting") }) + instance.reset() + } + + @Test + fun testDebugLogs_forClearCancelAndFactory() { + val instance = KRelay.builder("DebugLogScope").debugMode(true).build() + instance.registerActionFactory("toast", "show") { p -> { it.show(p) } } + instance.dispatch { it.show("x") } + instance.clearQueue(ToastFeature::class) + val token = scopedToken() + instance.dispatch(token) { it.show("y") } + instance.cancelScope(token) + + val all = logs.map { it.second } + assertTrue(all.any { it.contains("[FACTORY]") }) + assertTrue(all.any { it.contains("[CLEAR]") }) + assertTrue(all.any { it.contains("[CANCEL]") }) + instance.reset() + } + + // ── error isolation ────────────────────────────────────────────── + + @Test + fun testThrowingActionDuringReplay_isLogged_andOthersStillRun() { + val instance = KRelay.create("ReplayErrorScope") + val mock = MockToast() + instance.dispatch { error("boom") } + instance.dispatch { it.show("survivor") } + + instance.register(mock) + + assertEquals(listOf("survivor"), mock.shown) + assertTrue(logs.any { it.first == KRelayLogLevel.ERROR && it.second.contains("Error replaying") }) + instance.reset() + } + + @Test + fun testThrowingPriorityAction_isLogged() { + val instance = KRelay.create("PriorityErrorScope") + instance.register(MockToast()) + instance.dispatchWithPriority(ActionPriority.HIGH) { error("boom") } + + assertTrue(logs.any { it.first == KRelayLogLevel.ERROR }) + instance.reset() + } + + @Test + fun testThrowingPersistedAction_isLogged() { + val instance = KRelay.create("PersistedErrorScope") + instance.registerActionFactory("toast", "explode") { _ -> { error("boom") } } + instance.register(MockToast()) + + // payload passed explicitly: with two strings the call would bind to the deprecated overload + instance.dispatchPersisted("toast", "explode", "") + + assertTrue(logs.any { it.first == KRelayLogLevel.ERROR && it.second.contains("persisted dispatch") }) + instance.reset() + } + + // ── expiry on replay ───────────────────────────────────────────── + + @Test + fun testExpiredActionsAreDroppedOnRegister_andReported() { + val instance = KRelay.builder("ExpiryScope").actionExpiry(5).debugMode(true).build() + val mock = MockToast() + instance.dispatch { it.show("stale") } + spinFor(20) + + instance.register(mock) + + assertTrue(mock.shown.isEmpty()) + assertEquals(1L, KRelayMetrics.getExpiryCount(ToastFeature::class)) + assertEquals(1, events.single { it.type == KRelayMetricType.EXPIRY }.count) + assertTrue(logs.any { it.second.contains("[EXPIRY]") }) + instance.reset() + } + + @Test + fun testExpiredActionsAreSweptOnEnqueue() { + val instance = KRelay.builder("SweepScope").actionExpiry(5).build() + instance.dispatch { it.show("stale-1") } + instance.dispatch { it.show("stale-2") } + spinFor(20) + + instance.dispatch { it.show("fresh") } + + val mock = MockToast() + instance.register(mock) + assertEquals(listOf("fresh"), mock.shown) + instance.reset() + } + + @Test + fun testRestoredOldCommandsAreStillSweptOnEnqueue() { + val adapter = Recorder() + val instance = KRelay.builder("RestoredSweepScope").actionExpiry(10_000).build() + instance.setPersistenceAdapter(adapter) + instance.registerActionFactory("toast", "show") { p -> { it.show(p) } } + // Fresh enqueue first, then a restored command whose timestamp is older but not yet expired + instance.dispatch { it.show("new") } + adapter.store["toast"] = mutableListOf(PersistedCommand("show", "restored", currentTimeMillis() - 9_000)) + instance.restorePersistedActions() + instance.actionExpiryMs = 1_000 // restored command is now past its expiry + + instance.dispatch { it.show("trigger") } + + val mock = MockToast() + instance.register(mock) + assertFalse(mock.shown.contains("restored"), "expired restored command must be swept") + instance.reset() + } + + // ── persistence restore edge cases ─────────────────────────────── + + @Test + fun testRestore_skipsExpiredUnknownFeatureAndUnknownAction_andCleansStorage() { + val adapter = Recorder() + val instance = KRelay.builder("RestoreScope").actionExpiry(1_000).debugMode(true).build() + instance.setPersistenceAdapter(adapter) + instance.registerActionFactory("toast", "show") { p -> { it.show(p) } } + + val now = currentTimeMillis() + val fresh = PersistedCommand("show", "fresh", now) + val expired = PersistedCommand("show", "old", now - 10_000) + val noFactory = PersistedCommand("missing", "x", now) + val unknownFeature = PersistedCommand("show", "y", now) + adapter.store["toast"] = mutableListOf(fresh, expired, noFactory) + adapter.store["ghost"] = mutableListOf(unknownFeature) + + instance.restorePersistedActions() + + assertEquals(1, instance.getPendingCount()) + assertEquals(4, adapter.removed.size) + assertTrue(adapter.store.values.all { it.isEmpty() }) + assertTrue(logs.any { it.first == KRelayLogLevel.WARN && it.second.contains("No KClass for 'ghost'") }) + assertTrue(logs.any { it.first == KRelayLogLevel.WARN && it.second.contains("No factory for") }) + assertTrue(logs.any { it.second.contains("[RESTORE] Restored 1") }) + + val mock = MockToast() + instance.register(mock) + assertEquals(listOf("fresh"), mock.shown) + instance.reset() + } + + @Test + fun testRestore_withNothingPersisted_isNoOp() { + val instance = KRelay.builder("EmptyRestoreScope").debugMode(true).build() + instance.restorePersistedActions() + + assertTrue(logs.any { it.second.contains("No persisted actions") }) + instance.reset() + } + + @Test + fun testPersistedQueuedAction_replaysWhenFeatureRegisters() { + val instance = KRelay.create("PersistedReplayScope") + instance.registerActionFactory("toast", "show") { p -> { it.show(p) } } + instance.dispatchPersisted("toast", "show", "later") + val mock = MockToast() + + instance.register(mock) + + assertEquals(listOf("later"), mock.shown) + instance.reset() + } + + @Test + fun testDeprecatedPersistedOverloads() { + val instance = KRelay.create("DeprecatedScope") + instance.registerActionFactory("show") { p -> { it.show(p) } } + val mock = MockToast() + instance.register(mock) + instance.dispatchPersisted("show", "payload") + assertEquals(listOf("payload"), mock.shown) + + KRelay.registerActionFactory("toast", "show") { p -> { it.show(p) } } + KRelay.registerActionFactory("legacyShow") { p -> { it.show(p) } } + KRelay.register(mock) + KRelay.dispatchPersisted("toast", "show", "singleton") + KRelay.dispatchPersisted("legacyShow", "legacy") + KRelay.setPersistenceAdapter(InMemoryPersistenceAdapter()) + KRelay.restorePersistedActions() + + assertEquals(listOf("payload", "singleton", "legacy"), mock.shown) + instance.reset() + } + + @Test + fun testInMemoryPersistenceAdapter_isInert() { + val adapter = InMemoryPersistenceAdapter() + val command = PersistedCommand("a", "b") + adapter.save("s", "f", command) + assertTrue(adapter.loadAll("s").isEmpty()) + adapter.remove("s", "f", command) + adapter.clearScope("s") + adapter.clearAll() + } + + // ── diagnostics & metrics output ───────────────────────────────── + + @Test + fun testDebugInfoToString_includesExpiredAndQueues() { + val info = DebugInfo( + registeredFeaturesCount = 0, + registeredFeatures = emptyList(), + featureQueues = mapOf("ToastFeature" to 2), + totalPendingActions = 2, + expiredActionsRemoved = 3, + maxQueueSize = 100, + actionExpiryMs = 60_000, + debugMode = true + ) + val text = info.toString() + + assertTrue(text.contains("(none)")) + assertTrue(text.contains("ToastFeature: 2 events")) + assertTrue(text.contains("Expired & Removed: 3 events")) + assertTrue(text.contains("Debug Mode: true")) + } + + @Test + fun testDumpAndPrintReport_doNotThrow() { + KRelayMetrics.printReport() + val instance = KRelay.create("DumpScope") + instance.dispatch { it.show("x") } + instance.register(MockToast()) + instance.dispatch { it.show("y") } + instance.dump() + KRelay.dump() + KRelayMetrics.printReport() + instance.reset() + } + + @Test + fun testMetricsResetInstance_andEnabledExtension() { + KRelay.metricsEnabled = true + assertTrue(KRelay.metricsEnabled) + + val instance = KRelay.create("MetricsResetScope") + instance.register(MockToast()) + instance.dispatch { it.show("x") } + assertEquals(1L, KRelayMetrics.getInstanceMetrics("MetricsResetScope")["ToastFeature"]?.get("dispatches")) + + KRelayMetrics.resetInstance("MetricsResetScope") + assertTrue(KRelayMetrics.getInstanceMetrics("MetricsResetScope").isEmpty()) + instance.reset() + } + + @Test + fun testGetMetricsExtension_reportsAllCounters() { + KRelay.register(MockToast()) + KRelay.dispatch { it.show("x") } + + val metrics = KRelay.getMetrics() + assertEquals(1L, metrics["dispatches"]) + assertEquals(setOf("dispatches", "queued", "replayed", "expired", "cleared"), metrics.keys) + } +} From 227d5a502458b03d3a97281b9ab4521c763579ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?= Date: Thu, 1 Oct 2026 02:04:55 +0700 Subject: [PATCH 8/8] build: drop kotlin.native.cacheKind properties (removed in Kotlin 2.3.20, only produced deprecation warnings) --- gradle.properties | 5 ----- 1 file changed, 5 deletions(-) diff --git a/gradle.properties b/gradle.properties index fa759b7..11f15c2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,8 +14,3 @@ android.useAndroidX=true # 16KB page size compatibility for Android 15+ android.experimental.allow16KbPagesNativeLibs=true -# Disable Native Cache due to Compose Multiplatform 1.8.0 compiler NPE -kotlin.native.cacheKind=none -kotlin.native.cacheKind.iosSimulatorArm64=none -kotlin.native.cacheKind.iosArm64=none -kotlin.native.cacheKind.iosX64=none \ No newline at end of file